Security in Angular Applications

This tutorial is adapted from Web Age course Advanced Angular 8 Programming.

1.1 What is Authentication and Authorization?

Authentication is the process of reliably identifying a user. Authorization is the process of giving a user access to only the resources she is allowed to access. Some common programming tasks that need authenication and authorization are: Provide a login page where user enters her credentials, upon a successful login the server issues a token identifying the user-the browser side saves that token in memory or local storage, Limit access to application functions based on user privilege-this is usually done using role based security, With every HTTP call to the backend include the user identity token;depending on the technology used this is usually done as a cookie or HTTP header, If the user accesses a protected resource (such as an Angular route) and the identity token does not exist or has expired then redirect to the login page, If a HTTP call returns 403 or similar then redirect to the login page.

1.2 What is Identity Token?

Two most common ways to create an identity token are SAML2 and JWT. In the end they both achieve the same goals. An identity token issued by the server contains user's unique ID, name, e-mail address, various security roles and any other kind of business specific payload. The token is signed by the server. It is very difficult to generate a fake token by a third party. When a client sends a token with an HTTP request the server can reliably ensure that the token was issued by it and has not been tampered with. A token is however vulnerable to stealing via man in the middle attack. The server can not discern if a token was stolen. To prevent against that always transfer tokens over HTTPS and set a token to expire every few hours. SAML2 is widely used in the enterprise. But JWT is a more modern standard and much easier to use. In this tutorial we will use JWT in the examples but the basic approach will be same for SAML2.

1.3 How to exchange the Identity Token?

Upon a successful login the server will issue the identity token. The client needs to send this back in future with every HTTP request. Exactly how the server and client do this depends on various things. Here are two common scenarios:

  • Cookie based - The server expects the token to be in a cookie. In which case the server sets the cookie and the browser automatically sends it back with every request.

  • Header based - In JWT the client usually sends the token back in the "Authorization" request header. In that case the server issues the token in the response body of the login request. The client needs to write code to save that token and set the Authorization header for all future requests.

//Example response from server containing a JWT token
{
  token: "abc-123"
}

//Example Authorization request header
Authorization: Bearer abc-123

1.4 Example Login Code in Angular Service

Saves identity token in session storage

@Injectable(...)
export class AuthenticationService {
  constructor(private http:HttpClient) { }

  login(credentials:Object) : Observable<Object> {
    return this.http.post<Object>("/login", credentials)
      .pipe(tap(response => 
          window.sessionStorage.setItem("auth-token", 
          response)))
  }
  getAuthToken() : string {
    return window.sessionStorage.getItem("auth-token")
  }
}

Notes

The example above assumes that the identity token is available from the response of the "/login" call as follows:

{ token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.0gyt4I1ZFGzzDZBrK9jwOIH4NSWvs0k4snp-ZcwKLVU" }

1.5 What are the Options for Saving the Identity Token?

You can save the token in a service class member variable. Because Angular apps are single page the service instance is preserved as the user navigates between views. In session storage: This is per server domain and per tab. The storage is cleared when the tab is closed. In local storage. This is per domain and survives a browser restart.

1.6 How to send the Token to Server?

You can develop an interceptor to set the Authorization header for every backend call.

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authSvc: AuthenticationService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.authSvc.getAuthToken() === null) {
      return next.handle(req)
    }

    req = req.clone({
      setHeaders: { "Authorization": `Bearer ${this.authSvc.getAuthToken()}` }
    })

    return next.handle(req)
  }
}

1.7 How to Obtain User Information?

JWT allows the server to encode user information such as full name, profile photo URL and user roles in the identity token. This information is called payload of the token. You can decode the token to obtain the payload. Use the jwt-decode package to do this.

npm install jwt-decode --save

npm install @types/jwt-decode –save-dev
  • Example code.

import * as decode from 'jwt-decode'

export class UserProfile {
  photoURL:string
  name:string
  roles:string[]
}

let jwtToken = "ABC1728...2xcv"
let userProfile = decode<UserProfile>(jwtToken)

1.8 What is Cross Site Scripting (XSS) Attack?

This attack is mounted by a user by somehow embedding a <script> tag in a page. This can be done for example by submitting a comment or writing a user profile that contains the <script> tag. When another user visits the infected page, the script can then collect private information about that user from the DOM such as cookie, names and emails. This information then can be passed to a third party site using a mechanism that bypasses the same origin policy. Such as setting the src attribute of an image or adding a <script> tag with source from another site. XSS is one of most common forms of security breaches on the web. To prevent XSS reject any HTML content from untrusted sources. For example, you can severely limit or completely eliminate any HTML in user comments. But allow a company staff to allow entering HTML in product catalog content.

1.9 Angular and XSS

By default Angular considers values stored in any component variable as untrusted. But any HTML directly entered in the template is trusted. In this example, the <div> and <b> tags in the template are trusted. But the <h1> tag in the comment variable is not trusted. Angular will sanitize variables (escape the HTML) before setting DOM element properties.

@Component({...
  template: `<div><b>Comment:</b> {{comment}}</div>`
})
export class AppComponent {
  comment = '<h1>Hello World</h1>'
}

//Generates this DOM element
<div><b>Comment:</b> &lt;h1&gt;Hello World&lt;/h1&gt;</div>

1.10 How to Sanitize Content?

Angular uses different strategies to sanitize the value in a variable depending on where in the template the variable is used:

  • HTML - When the variable is used to set the innerHTML of an element. We see this in the example in the previous slide.

  • Style - When you bind the variable to the "style" property of an element.

  • URL - When you bind the variable to the "href" property.

  • Resource URL - When you bind the variable to the "src" attribute of an image or script.

A different strategy is used in these cases because an XSS attack is mounted differently in each of these situations.

1.11 How to Trust HTML Content?

To bypass Angular's sanitization for trusted content take these steps:

  • Convert a string to SafeHtml by calling the bypassSecurityTrustHtml() method of DomSanitizer service.

  • Bind the SafeHtml object to the innerHTML property of an element.

  • The same example as before converted to bypass sanitization.

import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Component({...,
  template: `<div><b>Comment:</b> <span  ="getSafeComment()"></span></div>`
})
export class AppComponent {
  comment = '<h1>Hello World</h1>'
  constructor(private sanitizer: DomSanitizer){}
  getSafeComment() : SafeHtml {
    return this
      .sanitizer
      .bypassSecurityTrustHtml(this.comment)
  }
}

1.12 What is Cross-Site Request Forgery (XSRF)?

In this attack a page from a malicious site (evil.com) somehow sends a request to a legitimate site (good.com). Such a request needs to bypass the same origin policy which is quite easy. For example, request an image from good.com or submit a form using JavaScript. If the user is currently logged into the legitimate site then such a request can cause all kinds of unintended consequence. The damage level increases with the level of privilege the user has with the legitimate site. XSRF attack is technically very easy to mount. The only challenge is in how to lure users into the malicious site. This is usually done through phishing attack. Details of XSRF and strategies to mitigate it are beyond the scope of this class. Read about it in www.owasp.org.

1.13 Preventing XSRF Using Angular

One of the strategies used to prevent XSRF is to set a custom request header from the client and verify its existence in the server side. Currently there is no way for a malicious web page to set custom headers prior to requesting an image or submitting a form to the legitimate web site.

Angular's HttpClient service has some support for this. To use this, take these steps:

  • The server must first issue a unique token using the XSRF-TOKEN cookie. set-cookie: XSRF-TOKEN=example-xsrf-token;Path=/

  • For all future POST, PUT, DELETE requests HttpClient will set the X-XSRF-TOKEN custom header. X-XSRF-TOKEN: example-xsrf-token

  • From the server side verify that X-XSRF-TOKEN custom header is set.

  • In addition, take all other mitigation steps outlined in OWASP web site. Like verify the Referer request header to make sure request is coming from a page served by the legitimate web site.

1.14 Summary

In this tutorial we covered:

  • Upon a successful authentication the server should issue an identity token. Angular app should save the token in memory, local or session storage depending on the business requirement.

  • To make any secure backend call the application needs to send that token back. Server validates the token to make sure that it was not made up by a third party or has not expired.

  • You can obtain user information such as name and email by decoding the token.

  • Angular provides ways to mitigate Cross Site Scripting and Cross Site Request Forgery attacks.

 

Karandeep Kaur October 30, 2019

Leave a Reply

Your email address will not be published. Required fields are marked *

Web age solutions blog Zones