← Back to Articles         
Angular + ASP.NET Core JWT Security

Angular + ASP.NET Core JWT Security

Published on 01 Oct 2026     10 min read Angular
Authentication

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 HttpOnly secure 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:

sessionStorage is 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.
  • localStorage is convenient but JavaScript-accessible.
  • XSS can potentially steal credentials stored in localStorage or sessionStorage.
  • sessionStorage does not fundamentally solve XSS exposure.
  • HttpOnly cookies 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.

← Back to Articles