fix: validate JWT expiration with clock skew tolerance - #3467
Open
sinchubhat wants to merge 1 commit into
Open
Conversation
sinchubhat
force-pushed
the
issue993-console
branch
from
July 27, 2026 05:29
bf7912a to
e12edc0
Compare
sinchubhat
force-pushed
the
issue993-console
branch
from
July 27, 2026 05:43
e12edc0 to
0565c91
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens session restore on app startup by validating locally stored JWTs (with a 5‑minute clock-skew tolerance) to prevent first-load 401s, and tightens the authorization interceptor’s login-endpoint detection.
Changes:
- Added client-side JWT
expvalidation with a 5-minute tolerance and an initialization gate (authStateInitialized$) to reduce startup race conditions. - Updated the authorize interceptor to only skip attaching a bearer token for the actual login endpoint (not other
/authorize/*routes). - Expanded unit tests for both the interceptor behavior and AuthService constructor token handling.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/app/authorize.interceptor.ts | Refines login-endpoint matching so only /api/v1/authorize is excluded from auth header injection. |
| src/app/authorize.interceptor.spec.ts | Updates/extends tests to cover the new login exclusion and /authorize/validate inclusion behavior. |
| src/app/auth.service.ts | Adds localStorage session restore with JWT expiration validation + clock-skew tolerance and an initialization observable to coordinate route guarding. |
| src/app/auth.service.spec.ts | Adds constructor-time token validation tests for valid/expired/malformed stored tokens. |
sinchubhat
force-pushed
the
issue993-console
branch
from
July 27, 2026 06:44
0565c91 to
b8ed23b
Compare
sinchubhat
force-pushed
the
issue993-console
branch
from
July 27, 2026 07:57
b8ed23b to
31e4719
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/app/auth.service.ts:210
- Use
localStorage.setItem()instead of property assignment (localStorage.loggedInUser = ...) for consistency with the rest of the codebase and to avoid relying on Storage property-mapping behavior. Examples using setItem/getItem:src/app/core/about/about.component.ts:38-44,src/app/core/toolbar/toolbar.component.ts:113-114.
if (!environment.useOAuth) {
this.isLoggedIn = true
localStorage.loggedInUser = JSON.stringify(data)
this.loggedInSubject$.next(this.isLoggedIn)
this.authStateInitialized$.next(true)
sinchubhat
marked this pull request as ready for review
July 27, 2026 14:31
Contributor
|
@sinchubhat Have you tested these changes with cloud deployment? |
Fixes first-load 401 errors on /api/v1/devices/stats when stale JWT exists in localStorage. Addresses device-management-toolkit/console#993
sinchubhat
force-pushed
the
issue993-console
branch
from
July 28, 2026 04:04
31e4719 to
4aebe3b
Compare
Contributor
Author
Hi, tested with cloud as well, updated the PR description with the curl commands. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes first-load 401 errors on /api/v1/devices/stats when stale JWT exists in localStorage.
Addresses device-management-toolkit/console#993
Root cause:
Original code didn't validate JWT expiration before trusting tokens from localStorage. In distributed systems, client/edge/backend clocks may differ due to timezone and NTP drift.
Solution:
Add JWT validation with 5-minute clock skew tolerance:
Changes:
Clock skew tolerance (5 minutes) handles:
Testing:
Prerequisites:
Browser and host/server time must match!
Check times match:
In terminal
In sample-web-ui browser F12 console
console.log(new Date().toString());If times differ by >5 minutes, fix before testing:
sudo timedatectl set-ntp trueUnit tests
Testing with Console
Backend API Tests (curl)
Test 1: Fresh Valid Token
Login and get token
Test with backend
Expected: HTTP 200 {"totalCount":0,"connectedCount":0,"disconnectedCount":0}
Test 2: Expired Token (10 minutes ago)
Create expired token
Test with backend
Expected: HTTP 401 {"error":"invalid access token"}
Test 3: Invalid/No Token
Malformed token
Expected: HTTP 401 {"error":"invalid access token"}
No token
curl -sk "$BASE/api/v1/devices/stats"Expected: HTTP 401 {"error":"request does not contain an access token"}
Testing with Cloud
Kong does the JWT validation and not MPS
Endpoints through Kong: /mps/login/api/v1/authorize and /mps/api/v1/devices/stats
cloud base url should be https://ipaddress:443/ and not http://localhost:3000
git clone -b v2 --recursive https://github.com/device-management-toolkit/deployment.git deployment-v2 cd deployment-v2 cp .env.template .envEdit .env and kong.yaml as mentioned in documentation https://device-management-toolkit.github.io/docs/2.36/GetStarted/Cloud/setup/
Testing via curl cmds
Test 1: Login and get valid token
Test 2: Valid token - should work
Expected: {"totalCount":0,"connectedCount":0,"disconnectedCount":0}
Test 3: Expired token (10 minutes ago) - should fail
Expected: HTTP 401 {"message":"No mandatory 'iss' in claims"}
Test 4: Malformed token - should fail
Expected: HTTP 401 {"message":"Bad token; invalid JSON"}
Test 5: No token - should fail
curl -sk "$BASE/mps/api/v1/devices/stats"Expected: HTTP 401 {"message": "Unauthorized"}
Manual Browser Tests
Test 1: Valid Token (Should Stay Logged In)
http://localhost:4200Expected: Stays logged in, dashboard loads
Test 2: Expired Token - Beyond Tolerance (Should Redirect to Login)
What this does: Creates token expired 10 minutes ago (beyond 5-min tolerance)
Expected:
/loginimmediatelyTest 3: Expired Token - Within Tolerance (Should Stay Logged In)
What this does: Creates token expired 3 minutes ago (within 5-min tolerance)
Expected behavior:
.x) → 401Note: The 401 error is from the fake signature, not expiration. The key proof that clock tolerance works is that dashboard loaded (unlike Test 2 where client rejected immediately before loading).
Helper:
Check Current Token Status
To see your token expiration, press F12 → Console → Run:
Debugging
Check Token Details
// In browser console
Monitor Network
/api/v1/devices/statsUnderstanding 5-Minute Tolerance
Why needed?
How it works:
Industry standard: OAuth 2.0, JWT RFC, Auth0, AWS, Google all use 5-10 minutes
PR Checklist
What are you changing?
Anything the reviewer should know when reviewing this PR?
If the there are associated PRs in other repositories, please link them here (i.e. device-management-toolkit/repo#365 )