Building a Secure Student Portal with WSO2 Identity Server: MFA, JWT Authentication, and Role-Based Access Control
1. Introduction Authentication and authorization are important aspects of modern web application security. Applications need to verify users' identities and ensure that each user can access only the resources they are
1. Introduction
Authentication and authorization are important aspects of modern web application security. Applications need to verify users' identities and ensure that each user can access only the resources they are permitted to use.
As a Cyber Security undergraduate, I wanted to explore how identity and access management technologies work in a practical application. To gain hands-on experience, I developed a Secure Student Portal prototype using WSO2 Identity Server.
The project integrates WSO2 Identity Server 7.3.0 with a React frontend and a Node.js/Express backend. It demonstrates several security concepts, including OpenID Connect (OIDC) authentication, multi-factor authentication (MFA), JWT access token validation, and role-based access control (RBAC).
The portal supports two demonstration roles: Student and Administrator. Students can access a protected student API, while administrative operations require additional permissions. The application also demonstrates how unauthorized requests are rejected by the backend.
This project is a local proof of concept designed for learning and demonstrating identity security rather than a production-ready student management system.
In this article, I will explain the technologies used, the implementation approach, the security tests performed, the challenges encountered, and the lessons learned while building the application.
2. Project Architecture and Technologies Used
2.1 Technology Stack
The Secure Student Portal was developed using the following technologies:
| Technology | Purpose |
|---|---|
| WSO2 Identity Server 7.3.0 | Identity management, authentication, and user role configuration |
| React with Vite | Frontend development and user interface |
| Node.js with Express | Backend REST API development |
| OpenID Connect (OIDC) | User authentication |
| OAuth 2.0 Authorization Code Flow with PKCE | Secure authentication flow for the React single-page application |
| JSON Web Tokens (JWT) | Representing access tokens used for protected API requests |
| JSON Web Key Set (JWKS) | Providing public keys for verifying JWT signatures |
| Role-Based Access Control (RBAC) | Restricting API operations according to user permissions |
| Google Authenticator | Testing time-based one-time password (TOTP) multi-factor authentication |
2.2 Application Architecture
The application follows a frontend, identity provider, and backend architecture.
The React frontend provides separate dashboards for students and administrators. It communicates with WSO2 Identity Server to authenticate users and obtains access tokens for calling protected APIs.
WSO2 Identity Server acts as the identity provider. It manages user identities, authentication, roles, and OAuth 2.0 scopes. TOTP-based multi-factor authentication was also configured and tested as part of the project.
The Node.js/Express backend exposes protected API endpoints. Before returning protected information, the backend verifies the access token's signature and relevant claims, then checks whether the token contains the required scope.
The project uses two demonstration roles:
-
Student: Can access the protected student API using the
students:readpermission. -
Portal Administrator: Can access the administrative API using the
students:managepermission.
This separation makes it possible to demonstrate authorization decisions using different user accounts.
2.3 Authentication and Authorization Flow
When a user selects the login option, the React application redirects the browser to WSO2 Identity Server.
After successful authentication, the application completes the OIDC authorization process using the Authorization Code Flow with PKCE.
The frontend then uses the obtained access token when making requests to the Express backend.
The backend validates the JWT before processing protected requests. It also checks the required OAuth 2.0 scope for the requested endpoint.
For example, the student account can successfully call the student API but receives an HTTP 403 Forbidden response when attempting to call the administrative API without the required permission.
This architecture demonstrates how centralized identity management and backend authorization checks work together to protect application resources.
The implementation is intended for a local demonstration. Additional hardening, including API-specific token audience validation and dependency security updates, is required before considering a production deployment.
2.4 System Architecture Diagram
The following diagram illustrates the authentication and authorization flow implemented in the Secure Student Portal prototype.
flowchart TD
A["Student / Administrator"] --> B["React Frontend"]
B -->|"OIDC Login with PKCE"| C["WSO2 Identity Server 7.3.0"]
C -->|"Authentication, MFA and Tokens"| B
B -->|"API Request with Access Token"| D["Node.js / Express Backend"]
D --> E["JWT Signature and Claims Validation"]
E -->|"Invalid or Missing Token"| F["401 Unauthorized"]
E -->|"Valid Token"| G{"Required Scope Present?"}
G -->|"No"| H["403 Forbidden"]
G -->|"students:read"| I["Student API - 200 OK"]
G -->|"students:manage"| J["Admin API - 200 OK"]
Note: This architecture represents a local proof of concept. JWT verification uses locally cached WSO2 public keys. API-specific audience validation requires further hardening before production use.
3. WSO2 Identity Server Configuration and Implementation
3.1 Setting Up WSO2 Identity Server
I started by setting up WSO2 Identity Server 7.3.0 in my local development environment.
After installing the required Java Development Kit (JDK), I downloaded and extracted WSO2 Identity Server. I then started the server and accessed its administration console through:
https://localhost:9443/console
The administration console provided the necessary tools to configure application authentication, user accounts, roles, and API authorization.
For this prototype, I created two test user accounts to demonstrate different access levels: a student account and an administrator account.
3.2 Registering the React Application
Next, I registered the Secure Student Portal as an application in WSO2 Identity Server.
Since the frontend was developed as a React single-page application (SPA), I used the OpenID Connect authentication protocol with the OAuth 2.0 Authorization Code Flow and Proof Key for Code Exchange (PKCE).
PKCE is particularly important for browser-based applications because it helps protect the authorization code exchange without requiring a confidential client secret in the frontend.
The React application runs locally at:
http://localhost:5173/
I configured the application to communicate with WSO2 Identity Server and integrated the WSO2 Asgardeo React SDK to manage authentication. After completing the configuration, I successfully tested the login and logout functionality.
3.3 Implementing Multi-Factor Authentication (MFA)
Password-based authentication alone may not provide sufficient protection against unauthorized access. If an attacker obtains a user's password, additional authentication controls can help prevent account compromise.
To explore this concept, I configured and tested Time-Based One-Time Password (TOTP) authentication using WSO2 Identity Server and Google Authenticator.
During the configuration process, I enrolled a test account for TOTP authentication and verified the setup using a time-sensitive verification code generated by the authenticator application.
This exercise helped me understand how a centralized identity provider can support additional authentication factors without requiring the application itself to generate or store one-time passwords.
MFA is an important security capability demonstrated in this project. However, the prototype does not include a complete frontend interface for reporting each user's live MFA enrollment status.
3.4 Configuring User Roles and API Scopes
One of the main security objectives of the project was to demonstrate how users with different permissions can access different backend resources.
I configured two demonstration roles in WSO2 Identity Server:
- student: Represents a regular student who should access only permitted student resources.
- portal-admin: Represents an administrator who can access protected management operations.
I also configured an API resource named Secure Student Portal API, using the resource identifier:
http://127.0.0.1:4000/api
The following OAuth 2.0 scopes were defined:
| Scope | Description |
|---|---|
students:read |
Allows access to the protected student API |
students:manage |
Allows access to protected administrative operations |
The permissions were configured so that student users could obtain the student read permission, while the administrator could obtain the management permission.
The important distinction in this implementation is that WSO2 manages the role and permission configuration, while the Express backend makes authorization decisions using the scopes included in the validated access token.
This provides a practical demonstration of role-based permission management combined with scope-based API authorization.
3.5 Building the Protected Node.js Backend
The backend was developed using Node.js and Express.
Its primary responsibility is to receive API requests, verify access tokens, and decide whether the requested operation should be allowed.
I created two protected API endpoints:
| API Endpoint | Required Scope |
|---|---|
GET /api/students |
students:read |
GET /api/admin/students |
students:manage |
The implementation uses the jose library to verify JWT access tokens.
Before granting access, the backend performs several checks:
- It checks whether the request contains a Bearer access token.
- It verifies the token's digital signature against a trusted WSO2 public key.
- It validates relevant JWT claims, including the issuer, expiration, and configured audience.
- It checks whether the token contains the scope required by the API endpoint.
- It returns an appropriate HTTP response depending on the validation and authorization result.
For local development, the application uses a cached JWKS file containing public verification keys.
A missing or invalid token is rejected with HTTP 401 Unauthorized. A valid token that lacks the required permission is rejected with HTTP 403 Forbidden.
A valid token with the required permission is allowed to access the requested endpoint.
Security consideration: The initial local prototype accepts an audience value associated with the registered frontend application. This was useful for demonstrating JWT validation, but it is not an appropriate final trust boundary for a production API. A production deployment should use a dedicated API audience and ensure that the backend accepts only access tokens specifically intended for that API. The application also needs a robust JWKS retrieval and key-rotation strategy.
3.6 Integrating the React Frontend
The user interface was developed using React and Vite.
I created separate student and administrator dashboards to demonstrate different access experiences.
The frontend uses the WSO2 Asgardeo React SDK to integrate the authentication flow with WSO2 Identity Server.
After a successful login, the application can make authenticated requests to the protected backend API.
I also created an API security testing component that allows the user to trigger requests and observe the resulting HTTP status codes.
This made it easier to verify authentication and authorization behavior directly from the browser.
The dashboard contains demonstration interface elements for areas such as student profiles, courses, and results. These sections are currently UI placeholders rather than a fully implemented academic records system.
The primary focus of the prototype is identity management and API access control.
4. Security Testing and Validation
After completing the authentication and authorization configuration, I performed several tests to verify how the application responds to different access scenarios.
4.1 Test Scenarios and Results
| Test Case | Expected Result | Observed Result | Status |
|---|---|---|---|
| API request without an access token | 401 Unauthorized | HTTP 401 | Passed |
| API request with an invalid access token | 401 Unauthorized | HTTP 401 | Passed |
| Student accesses the student API | 200 OK | HTTP 200 | Passed |
| Student attempts to access the admin API | 403 Forbidden | HTTP 403 | Passed |
| Administrator accesses the admin API | 200 OK | HTTP 200 | Passed |
These tests confirmed that the prototype rejected requests without acceptable tokens and enforced the configured scope restrictions in the tested scenarios.
4.2 Student Access Test — HTTP 200
After logging in with the student account, I tested the protected student API.
The request was successful because the access token contained the required students:read permission.
The backend returned HTTP 200 OK.
This demonstrated that authenticated users with the required permission could access a protected resource.
4.3 Unauthorized Student Access — HTTP 403
Next, I attempted to access the administrative API while logged in as a student.
The request was rejected because the student's token did not contain the required students:manage scope.
The backend returned HTTP 403 Forbidden.
This was one of the most important tests in the project because it demonstrated that being authenticated does not automatically authorize a user to perform administrative operations.
4.4 Administrator Access Test — HTTP 200
I then logged in using the administrator account and tested the protected administrative API.
Because the administrator's token contained the required management permission, the request was successful.
The backend returned HTTP 200 OK.
Together, these results demonstrated that the same backend could enforce different access decisions depending on the permissions available to the authenticated user.
4.5 Authentication Error Handling
I also tested requests that did not contain a valid access token.
The backend returned HTTP 401 Unauthorized when a request was made without a token or with an invalid token.
These tests helped confirm that the API did not simply trust requests based on frontend login state.
Instead, the backend performed its own token verification before granting access.
The successful test results demonstrate the expected behavior for the scenarios tested. They do not, by themselves, establish that the application is secure against every possible attack.
5. Challenges Faced and Lessons Learned
Developing this project was a valuable learning experience because several issues required investigation and debugging.
5.1 JWT Audience Validation
One challenge involved JWT audience validation.
Initially, the backend expected an API-specific audience, but the token available during local testing contained the registered application client identifier as its audience.
This caused token verification to fail.
To continue testing the prototype, I adjusted the expected audience to match the locally issued token.
Although this allowed the learning demonstration to proceed, I recognized that accepting a frontend-targeted token is not an appropriate long-term security design for a protected API.
A key lesson was that successful signature verification alone is not enough. The intended recipient of a token must also be validated correctly.
Future work will focus on configuring API-specific access tokens and enforcing the appropriate audience value.
5.2 Cross-Origin Resource Sharing (CORS)
Another challenge involved communication between the React frontend and Express backend.
The frontend runs on:
http://localhost:5173
The backend runs on:
http://127.0.0.1:4000
Because these are different origins, the browser applies Cross-Origin Resource Sharing (CORS) rules.
I investigated the failed browser requests and updated the backend CORS configuration to permit the expected local frontend origin and authorization headers.
This helped me understand how browser security policies affect API integration.
I also learned that CORS is not a replacement for authentication or authorization. The backend must still validate tokens and enforce access permissions.
5.3 Understanding the Difference Between Authentication and Authorization
At the beginning of the project, one of the most important concepts to understand was the difference between authentication and authorization.
Authentication answers the question:
Who is the user?
Authorization answers a different question:
What is the user allowed to do?
The student API and admin API tests helped make this distinction clear.
A student could successfully log in through WSO2 Identity Server while still being denied access to administrative operations.
This demonstrated why protecting an application requires more than a successful login process.
5.4 Dependency Security
As part of the development process, I also reviewed application dependencies using npm audit.
The backend production dependency audit did not report vulnerabilities at the time of testing.
However, the frontend audit reported security advisories involving transitive dependencies in the authentication SDK dependency tree.
Rather than applying potentially breaking dependency upgrades without verification, I treated these findings as unresolved items requiring further investigation.
This experience reinforced the importance of software supply chain security, dependency monitoring, and testing updates before deployment.
6. Project Screenshots and Demonstration
The following screenshots were captured during local testing.
6.1 Student Dashboard
The student dashboard provides the interface for an authenticated student user.
6.2 Admin Dashboard
The administrator dashboard demonstrates the separate administrative experience.
6.3 Student API — Successful Access
The student successfully accesses the protected student API and receives HTTP 200.
6.4 Student API — Access Denied
The student attempts to access the administrative API and receives HTTP 403 Forbidden.
6.5 Administrator API — Successful Access
The administrator accesses the protected administrative API and receives HTTP 200.
6.6 Multi-Factor Authentication
The MFA screenshot demonstrates the TOTP verification functionality tested through WSO2 Identity Server.
A separate local demo video was also recorded to demonstrate the student and administrator workflows.
7. Limitations and Future Improvements
Although the Secure Student Portal prototype demonstrates important identity and authorization concepts, several improvements are required before it could be considered suitable for production use.
7.1 API-Specific Token Audience Validation
The backend should validate access tokens intended specifically for the protected API rather than relying on the frontend application's audience.
This is an important improvement for preventing tokens intended for another recipient from being accepted by the API.
7.2 Dynamic JWKS Retrieval and Key Rotation
The current local implementation uses cached public verification keys.
A production implementation should securely retrieve verification keys from the identity provider and support key rotation and cache refresh procedures.
7.3 Dependency Vulnerability Remediation
The identified frontend dependency advisories should be investigated and addressed through compatible updates or vendor-supported fixes.
Any changes should be tested to ensure authentication functionality remains operational.
7.4 Persistent Data Storage
The current protected API endpoints primarily demonstrate authentication and authorization behavior.
Future work could include a database for student information, academic records, and administrative operations, with appropriate object-level authorization controls.
7.5 Comprehensive Security Testing
Further testing should cover additional threats and edge cases, including:
- Expired and malformed access tokens
- Incorrect token audiences and issuers
- Permission changes and token lifecycle handling
- Object-level authorization failures
- Security logging and monitoring
- Rate limiting and error handling
7.6 Production Deployment
The prototype currently runs in a local development environment.
A future deployment should include trusted TLS certificates, appropriate secret management, hardened identity provider configuration, secure environment variables, and controlled network access.
These improvements would help transform the learning prototype into a more robust application.
8. Conclusion
Building the Secure Student Portal using WSO2 Identity Server helped me gain practical experience with identity and access management concepts.
Rather than learning authentication and authorization only through theory, I was able to configure an identity provider, integrate it with a React application, and protect backend API endpoints using JWT validation and OAuth 2.0 scopes.
One of the most valuable outcomes was seeing how the backend responded to different security scenarios.
The student account could access the permitted student API but could not access the administrative API. The administrator account could access the protected administrative endpoint with the required permission.
Implementing and testing multi-factor authentication also helped me better understand how an additional verification factor can strengthen identity security.
I faced several integration challenges, particularly involving JWT audience validation and CORS configuration. Resolving these issues improved my understanding of authentication protocols, API security, and practical debugging.
The project remains a local proof of concept, with further security hardening required before production use.
Overall, this experience strengthened my interest in cybersecurity engineering and encouraged me to continue exploring WSO2 technologies, secure application development, and identity security.
9. References
- WSO2 Identity Server Documentation — https://is.docs.wso2.com/
- WSO2 Official Website — https://wso2.com/identity-server/
- OAuth 2.0 Authorization Framework (RFC 6749) — https://www.rfc-editor.org/rfc/rfc6749
- Proof Key for Code Exchange (RFC 7636) — https://www.rfc-editor.org/rfc/rfc7636
- JSON Web Token (RFC 7519) — https://www.rfc-editor.org/rfc/rfc7519
- OpenID Connect Core Specification — https://openid.net/specs/openid-connect-core-1_0.html
- React Documentation — https://react.dev/
- Express.js Documentation — https://expressjs.com/
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.




