Angular + ASP.NET Core JWT Security
These questions are closely related. The most important principle is:
Angular manages the user experience, but ASP.NET Core is the security boundary.
1. How Should Logout Work?
What is Logout?
Logout should terminate the application's authenticated session as effectively as the chosen token architecture allows.
With JWT authentication, there are two credentials to consider:
Access Token → short-lived
Refresh Token → longer-lived
A robust logout normally:
User clicks Logout
↓
Angular calls logout endpoint
↓
ASP.NET Core revokes refresh token/session
↓
Server clears refresh-token cookie if used
↓
Angular clears client authentication state
↓
Redirect to /login
Recommended Design
If you use:
- access token in Angular memory
- refresh token in an
HttpOnlysecure cookie
then logout is straightforward.
ASP.NET Core
[Authorize]
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
var userId =
User.FindFirstValue(
ClaimTypes.NameIdentifier);
await _refreshTokenService
.RevokeAllForUserAsync(userId!);
Response.Cookies.Delete(
"refreshToken");
return NoContent();
}
In a larger system, you might revoke only the current session/device rather than every refresh token belonging to the user.
Angular
logout() {
return this.http
.post(
`${this.apiUrl}/logout`,
{},
{
withCredentials: true
}
)
.pipe(
finalize(() => {
this.accessToken.set(null);
this.router.navigate([
'/login'
]);
})
);
}
What About the Access Token?
A JWT access token is usually self-contained.
If it has already been issued:
Access Token
Expires in 15 minutes
simply logging out does not automatically invalidate that JWT unless the server maintains additional revocation/session state and checks it.
That's one reason access tokens should generally be short-lived.
A common model is:
Logout
↓
Revoke Refresh Token
↓
No more Access Tokens can be issued
↓
Existing Access Token eventually expires
2. Where Should Tokens Be Stored?
There isn't one universally correct answer; it depends on the application's threat model and architecture.
For a browser SPA, a strong pattern is:
Access Token
↓
Angular memory
Refresh Token
↓
Secure HttpOnly Cookie
For example:
Browser
│
├── Angular memory
│ └── Access Token
│
└── HttpOnly Secure Cookie
└── Refresh Token
Access Token in Memory
Angular can keep the access token in a service/signal:
@Injectable({
providedIn: 'root'
})
export class AuthService {
accessToken =
signal<string | null>(null);
}
The interceptor can read it:
const authService =
inject(AuthService);
const token =
authService.accessToken();
if (token) {
req = req.clone({
setHeaders: {
Authorization:
`Bearer ${token}`
}
});
}
return next(req);
The advantage is that the token isn't persisted in browser storage.
The trade-off is that refreshing/reopening the page loses the in-memory access token. A refresh cookie can be used to obtain another access token.
3. What Are the Security Risks of localStorage?
Main Risk: XSS
localStorage is accessible to JavaScript running on the page.
For example:
const token =
localStorage.getItem(
'accessToken'
);
Your legitimate Angular code can access it.
Unfortunately, malicious JavaScript executing through an XSS vulnerability can potentially access it too.
Conceptually:
XSS vulnerability
↓
Malicious JavaScript executes
↓
localStorage.getItem(...)
↓
Token stolen
↓
Token may be replayed
against the API
For example, the attacker doesn't need the user's password if they successfully steal a usable bearer token.
Why Refresh Tokens Are More Sensitive
Suppose:
Access Token
Lifetime = 15 minutes
Refresh Token
Lifetime = 7 days
If the refresh token is stolen, it may allow the attacker to keep obtaining new access tokens until the refresh credential expires or is revoked.
Therefore, persisting a long-lived refresh token in:
localStorage
deserves particular caution.
4. Browser Storage vs Secure Cookies
This is an important interview topic.
localStorage
Example:
localStorage.setItem(
'accessToken',
token
);
Advantages
- Very easy to implement.
- Persists across page refreshes and browser restarts.
- Easy for Angular services/interceptors to read.
- Straightforward for Bearer-token APIs.
Disadvantages
The major security issue is:
JavaScript can read it
↓
XSS can potentially steal it
It also persists until explicitly removed or otherwise cleared.
sessionStorage
Example:
sessionStorage.setItem(
'accessToken',
token
);
It is generally scoped to a browser tab/session.
However:
sessionStorageis still JavaScript-accessible.
Therefore it does not solve the fundamental XSS token-theft problem.
localStorage
↓
JavaScript accessible
sessionStorage
↓
JavaScript accessible
HttpOnly Secure Cookie
ASP.NET Core can create a cookie:
Response.Cookies.Append(
"refreshToken",
refreshToken,
new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires =
DateTimeOffset.UtcNow
.AddDays(7)
});
The important setting is:
HttpOnly = true
Client JavaScript cannot directly read an HttpOnly cookie.
Angular cannot do:
document.cookie
to obtain that HttpOnly token value.
That reduces the risk of direct credential theft through XSS.
But cookies introduce another important security consideration:
CSRF (Cross-Site Request Forgery)
because browsers can automatically send cookies with matching requests.
Cookie-based designs therefore need appropriate protections such as:
SameSite- CSRF/antiforgery defenses where required
- strict origin/CORS configuration
Secure- HTTPS
Also, HttpOnly does not make XSS harmless: injected JavaScript may still be able to make authenticated requests from the victim's browser.
Comparison
| Feature | localStorage |
sessionStorage |
HttpOnly Cookie |
|---|---|---|---|
| JavaScript can read credential | Yes | Yes | No |
| Persists after browser restart | Yes | Usually no | Configurable |
| Main concern | XSS/token theft | XSS/token theft | CSRF + XSS actions |
| Automatically sent by browser | No | No | Yes |
| Angular can manually read token | Yes | Yes | No |
HttpOnly protection |
No | No | Yes |
Secure option |
N/A | N/A | Yes |
SameSite support |
N/A | N/A | Yes |
A Practical SPA Architecture
One useful architecture is:
Angular
│
Login credentials
│
▼
ASP.NET Core API
│
┌───────┴────────┐
│ │
Access Token Refresh Token
│ │
▼ ▼
Angular Memory HttpOnly Cookie
│
│ Authorization:
│ Bearer <token>
▼
API
When the access token expires:
Access Token expires
↓
API returns 401
↓
Angular calls /refresh
↓
Browser automatically sends
HttpOnly refresh cookie
↓
ASP.NET Core validates refresh token
↓
New access token
↓
Angular stores it in memory
↓
Continue
This avoids exposing the refresh token directly to Angular JavaScript.
Important Cookie Configuration
A refresh cookie might be configured as:
var cookieOptions =
new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite =
SameSiteMode.Strict,
Expires =
DateTimeOffset.UtcNow
.AddDays(7)
};
Response.Cookies.Append(
"refreshToken",
refreshToken,
cookieOptions);
HttpOnly
HttpOnly = true
Prevents normal client-side JavaScript from reading the cookie.
Secure
Secure = true
Restricts cookie transmission to HTTPS.
SameSite
SameSite = SameSiteMode.Strict
Can help mitigate cross-site request attacks, though the appropriate SameSite setting depends on how the frontend and API are deployed.
5. Why Must ASP.NET Core Perform Authorization Even When Angular Has Route Guards?
This is one of the most important security concepts.
Consider:
{
path: 'admin',
component: AdminDashboard,
canActivate: [
adminGuard
]
}
It may look like /admin is protected.
But Angular runs:
Inside the user's browser
The user controls that environment.
An attacker does not have to use your Angular application.
They could call your API directly using an HTTP client:
DELETE /api/products/100
Authorization: Bearer <token>
The Angular guard is never involved.
Attacker
│
│ Direct HTTP Request
│
▼
ASP.NET Core API
Therefore ASP.NET Core must independently perform:
Authentication
+
Authorization
Correct Architecture
Angular:
{
path: 'admin',
component: AdminDashboard,
canActivate: [
authGuard,
adminGuard
]
}
ASP.NET Core:
[Authorize(Roles = "Admin")]
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
...
}
Now:
Angular Route Guard
↓
Controls navigation / UX
ASP.NET Core
[Authorize(Roles = "Admin")]
↓
Enforces actual authorization
Example Attack
Suppose Angular hides the Delete button:
@if (isAdmin) {
<button>
Delete
</button>
}
A normal non-admin user doesn't see:
Delete
But hiding a button is not authorization.
The user could manually send:
DELETE https://api.example.com/api/products/10
If the backend has:
[Authorize(Roles = "Admin")]
ASP.NET Core checks the authenticated identity.
If the user isn't Admin:
403 Forbidden
That is actual security enforcement.
Angular vs ASP.NET Core Responsibility
| Angular | ASP.NET Core |
|---|---|
| Hide/show menus | Validate JWT |
| Hide/show buttons | Validate authentication |
| Route guards | Check roles |
| Redirect to login | Check claims |
| Redirect to unauthorized | Evaluate policies |
| Improve UX | Protect data |
| Client-side navigation | Server-side authorization |
| Not security boundary | Security boundary |
Remember:
Angular asks:
"Should I show/allow this UI?"
ASP.NET Core asks:
"Is this request actually authorized?"
Complete Security Flow
User logs in
↓
ASP.NET Core
↓
Access Token
+
Refresh Token
↓
┌─────────────────────────────┐
│ Browser │
│ │
│ Access Token → Memory │
│ │
│ Refresh Token → HttpOnly │
│ Cookie │
└─────────────────────────────┘
↓
Angular route guard
↓
Allow UI navigation
↓
Angular calls API
↓
Authorization:
Bearer <AccessToken>
↓
ASP.NET Core
↓
Validate JWT
↓
Authentication
↓
Authorization
↓
Role / Claim / Policy
↓
Controller
Logout:
Logout
↓
Revoke refresh session/token
↓
Delete refresh cookie
↓
Clear access token
↓
Redirect /login
Key Points
- Logout should remove client authentication state and invalidate/revoke server-side refresh credentials when applicable.
- Removing an access token from Angular doesn't necessarily invalidate an already-issued JWT.
- Keep access tokens relatively short-lived.
- Refresh tokens are longer-lived and deserve stronger protection.
localStorageis convenient but JavaScript-accessible.- XSS can potentially steal credentials stored in
localStorageorsessionStorage. sessionStoragedoes not fundamentally solve XSS exposure.HttpOnlycookies cannot be directly read by Angular JavaScript.- Cookies require careful CSRF,
SameSite, CORS/origin, and HTTPS configuration. - One useful SPA design is access token in memory + refresh token in an HttpOnly Secure cookie.
- Angular route guards provide navigation and UX control.
- ASP.NET Core must enforce actual authorization.
- Never assume that hiding a button, menu, or route protects an API.
- Always protect sensitive API operations using
[Authorize], roles, claims, or policies.
Interview Questions & Answers
1. How should logout work with JWT?
The client clears its authentication state, while the server revokes the refresh token/session and clears any refresh-token cookie. Existing self-contained access tokens may remain valid until expiration unless the server implements additional revocation checks.
2. Where should tokens be stored?
There is no universal answer. For browser SPAs, one strong design is to keep short-lived access tokens in memory and refresh credentials in appropriately configured HttpOnly, Secure cookies.
3. Why is storing JWT in localStorage risky?
Because localStorage is accessible to JavaScript. If an attacker successfully executes JavaScript through XSS, they may be able to read and exfiltrate the stored bearer token.
4. Is sessionStorage safer than localStorage?
It has different persistence behavior, but both are accessible to JavaScript. Therefore both remain exposed to token theft through successful XSS.
5. Why use HttpOnly cookies?
HttpOnly prevents JavaScript from directly reading the cookie value, reducing direct token theft through XSS.
6. What is the disadvantage of cookie-based authentication?
Because browsers automatically attach cookies, cookie-based authentication needs appropriate CSRF protections and careful SameSite, origin/CORS, HTTPS, and cookie configuration.
7. Why keep access tokens short-lived?
If an access token is stolen, a short lifetime limits its normal period of usefulness.
8. What should happen to the refresh token during logout?
The server should revoke/invalidate the relevant refresh token or session, and any corresponding refresh cookie should be removed.
9. Why aren't Angular route guards enough?
Angular executes in the browser and can be bypassed. An attacker can call the Web API directly without going through Angular routing.
10. What is the real security boundary?
The backend:
ASP.NET Core Web API
It must validate authentication and enforce authorization for every protected resource.
Interview Quick Answer
Angular route guards and UI checks improve navigation and user experience, but they are not a security boundary. ASP.NET Core must validate the authenticated identity and enforce roles, claims, or policies because clients can bypass Angular and call APIs directly. For token handling, short-lived access tokens combined with carefully protected refresh credentials can reduce risk; browser storage and cookie approaches each have different XSS and CSRF trade-offs.