Custom Header information in "@angular/http" request

Team,

I have spent a few hours looking at this but can hardly understand why I am getting my expected results. Now I hate to waste time on stuffs, especially when I know you gurus are out there.

I have successfully implemented JWT tokens authentication on my server side application and can receive tokens on the mobile client. However, when I attached that token on the header and make a request to the server, the token does not get to the server, I can see that the token is attached to the header when I look at my logs files.

Here is the code that sends the request to my server API with attached header.

import { Component } from '@angular/core';
import { NavController, Loading, ModalController, Events } from 'ionic-angular';
import { AccountType, AccountStatus, TransactionStatus } from '../../models//enum';
import { TranslatePipe, TranslateService } from "ng2-translate/ng2-translate";
import { HelperService } from '../../providers/helperService';
import { AccountService } from '../../providers/accountService';
import { User, Account, Transaction, Data} from '../../models/domain';
import { ReloadPage } from '../reload/reload';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/retry';

@Component({
  templateUrl: 'build/pages/accounts/accounts.html',
  providers: [AccountService],
  pipes: [TranslatePipe]
})
export class AccountsPage {

  queryText: string;
  accounts: Account[] = new Array<Account>();
  userClaim: any;

  constructor(
    private nav: NavController,
    private accountService: AccountService,
    private modalController: ModalController,
    private events: Events
  ) {

  }

  // ViewLoaded event
  ionViewLoaded() {
    this.accountService.getUserClaim()
      .then((userClaim) => {
        this.userClaim = userClaim;
        this.getAccounts();
      });
  }

  onclick() {
    let reloadModal = this.modalController.create(ReloadPage);
    reloadModal.present();
  }

  /**
   * Gets all user accounts from the server and hand over transaction processing
   * to processAccounts method.
   */
  getAccounts() {
    this.accountService.getAccounts(this.userClaim.user_id)
      .map(res => res.json())
      .subscribe(
      data => {
        this.accounts = data._embedded.accounts;
      },
      error => this.handleAccountProcessingError(error),
      () => {
        this.processAccounts(this.accounts);
      });
  }

  /**
   * Gets transactions for all user accounts from the server and hand over
   * processing of transaction to sortTransactions method.
   */
  processAccounts(accounts: Account[]) {
    if (accounts != null) {
      let transactions: Array<Transaction> = null;
      accounts.forEach(account => {
        this.accountService.getResource(account._links.transactions.href)
          .map(res => res.json())
          .subscribe(
          data => {
            transactions = data._embedded.transactions;
          }, error => {
            this.handleTransactionProcessingError(error)
          }, () => {
            this.sortTransactions(account, transactions);
          });
      });
    }
  }

  /**
   * Sort transaction list for an account into pending and posted accounts so for rendering 
   * on the view layer. If performance is an issue on the client the we may want to move
   * this process to the server side code.
   */
  sortTransactions(account: Account, transactions: Array<Transaction>) {
    let posted: Array<Transaction> = Array<Transaction>();
    let pending: Array<Transaction> = Array<Transaction>();
    transactions.forEach(transaction => {
      if (transaction.status == "PENDING") {
        pending.push(transaction);
      } else if (transaction.status == "POSTED") {
        posted.push(transaction);
      }
    })
    account.posted = posted;
    account.pending = pending;
  }

  /**
   * Logs error that may occur during account processing
   */
  handleAccountProcessingError(error: any) {
    console.log("An error occurred while processing user account data from the server " + error)
  }

  /**
   * Logs error that may occur during transaction processing
   */
  handleTransactionProcessingError(error: any) {
    console.log("An error occurred while processing user account data from the server " + error)
  }
}

The service class:

import { Injectable } from '@angular/core';
import { HelperService } from '../providers/helperService';
import { Http, Headers, Response, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { JwtHelper } from 'angular2-jwt';
import { Storage, LocalStorage } from 'ionic-angular';

@Injectable()
export class AccountService {

    private local: Storage;
    private headers: Headers;
    private jwtHelper: JwtHelper;

    constructor(private http: Http) {
        this.headers = new Headers();
        this.local = new Storage(LocalStorage);
        this.jwtHelper = new JwtHelper();
    }

    /**
     * Fetches user canonical from the server side
     * using unique username value stored in the token
     */
    getUser(userId: string): Observable<Response> {
        return this.getData(HelperService.BASE_URL_API + "/users/" + userId);
    }

    getAccounts(userId: string): Observable<Response> {
        return this.getData(HelperService.BASE_URL_API + "/accounts/search/findByProfileUserId?id=" + userId);
    }

    getTransactions(accountId: string): Observable<Response> {
        return this.getData(HelperService.BASE_URL_API + "/transactions/search/findByAccountId?id=" + accountId);
    }

    /**
     * Fetches resource information from the server for a particular User
     */
    getResource(resourceUrl: string): Observable<Response> {
        return this.getData(resourceUrl);
    }

    patchTransaction(user: any): Observable<Response> {
        return this.patchData(HelperService.BASE_URL_API + "/users/", user);
    }

    buildHeaders() {
        let headers = new Headers({
            'Accept': HelperService.CONTENT_TYPE.split(';')[0].replace("'", ""),
            'Content-Type': HelperService.CONTENT_TYPE
        });
        return this.local.get(HelperService.AUTHORIZATION)
            .then((token) => {
                headers.append(HelperService.AUTHORIZATION, token);
                console.log(headers);
                return headers;
            });
    }

    getData(url) {
        return Observable
            .fromPromise(this.buildHeaders())
            .switchMap((headers) => this.http.get(url, new RequestOptions({ headers: headers })));
    }

    getUserClaim() {
        return this.local.get(HelperService.AUTHORIZATION)
            .then((token) => {
                return this.jwtHelper.decodeToken(token);
            });
    }

    patchData(url, data) {
        return Observable
            .fromPromise(this.buildHeaders())
            .switchMap((headers) => this.http.patch(url, data, new RequestOptions({ headers: headers })));
    }

    putData(url, data) {
        return Observable
            .fromPromise(this.buildHeaders())
            .switchMap((headers) => this.http.put(url, data, new RequestOptions({ headers: headers })));
    }
}

The header information seen on my network tab of chrome browser developer tools for unsuccessful request where the custom header information does not reach the server.

Here is the logs on my server side code, I printed out header information to see if my custom header information reaches the server:

When I make the same request from postman the server side log looks like this:

Notice the content-type= application/json and x-auth-token = xxxxxxxxxxxxx

I know I am missing something but again I am new to ionic and angular world.

Thanks

Permit me to answer my own question here with the hope that someone can benefit from it. It turns out that I had to deal with multiple issues to resolve this.

  1. CORS can be a pain in the behind - I had to make sure that RequestMethod.OPTIONS is enabled or allowed to pass through without authentication or authorization on my server-side code. When a request is initiated by the client code for resources on the server, two things happen.

1.1 A request of type OPTIONS is sent to the server to verify whether the incoming request is allowed by the server side rules. In this request, no token or authentication information is allowed, as a matter fact the documentation states that this information is tripped out. For this to work, server-side code must allow RequestMethod.OPTIONS to work without verification.
1.2 Secondly, the header must contain authentication information so that when the second request, the actual request arrives at the server, it should be authenticated accordingly.

In my case, I was failing at the first request because my security rules did not provision for RequestMethod.OPTIONS to pass through without authentication.

do you get an error in your console about headers?

if your api is written in php add this to the .htaccess to see if it helps. Best way to see if its a CORS issue, just run in from a device, if that works you have a cors issue.

Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Methods "GET,POST,OPTIONS,DELETE,PUT"
Header add Access-Control-Allow-Headers "Content-Type"
Header add Access-Control-Allow-Headers "Authorization"