Angular 6 do I have to generate jwt in client to match server or just the server

Viewed 1361

I am a total newbie to jwt and I thank you for your patience.

I am generating jwt in PHP with the following:

// Create token header as a JSON string
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);

// Create token payload as a JSON string
$payload = json_encode(['username' => $this->username, 'password' => $this->password]);

// Encode Header to Base64Url String
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));

// Encode Payload to Base64Url String
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));

// Create Signature Hash
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, '<big secret>', true);

// Encode Signature to Base64Url String
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

All of that was just to generate the signature - $base64UrlSignature - which is then set to the key:

$this->key = $base64UrlSignature;

        $token = array(
            "iss" => $this->iss,
            "aud" => $this->aud,
            "iat" => $this->iat,
            "nbf" => $this->nbf,
            "data" => array(
                "username" => $this->username,
                "password" => $this->password
                )
        );

        // generate jwt
        $this->jwt = JWT::encode($token, $this->key);
        echo json_encode(
            array(
                "status" => 401,
                "id" => 0,
                "uName" => "Guest",
                "isAdmin" => "0",
                "ts" => "2018-12-28 00:00:00",
                "loggedIn" => false,
                "msg" => "Combination of username and password not found",
                "jwt" => $this->jwt
            )
        );

My question is, do I have to do the same on the client side in Angular, to see if it matches with the one generated by the server?

So far all that I have read, has to do with the server generating the jwt, then in Angular do the following:

localStorage.setItem("jwt", res.jwt);

Just place in local storage. Is that all there is to it?

Shouldn't there be a comparison of the token generated by the server to the token generated by the client?

If so, then how do I translate the above code to do so? For instance, what is the equivalent in Angular to Google's jwt PHP class:

$this->jwt = JWT::encode($token, $this->key);

I am adding code to the original post. The following is the interceptor that I have made:

import { ShoppingCartValuesService } from "./shopping-cart-values.service";
import { Injectable, Injector } from "@angular/core";
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest
} from "@angular/common/http";
import { Observable } from "rxjs/";
import * as jwt_decode from "jwt-decode";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(public srvc: ShoppingCartValuesService) {}
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const idToken = localStorage.getItem("jwt");

    if (idToken) {
      const cloned = req.clone({
        headers: req.headers.set("Authorization", "Bearer " + idToken)
      });
      console.log("Decoded jwt token: ", this.getDecodedAccessToken(idToken));
      return next.handle(cloned);
    } else {
      return next.handle(req);
    }
  }

  getDecodedAccessToken(token: string): any {
    try {
      return jwt_decode(token);
    } catch (error) {
      console.log("error: ", error);
      return false;
    }
  }
}

Which is called anytime a click is made in the links of the navbar via the click handler checkRequest() that is passed the route and later prepends the entire URL:

  <li class="nav-item">
    <a
      class="nav-link"
      data-toggle="tooltip"
      title="Products"
      routerLink="/products"
      id="products"
      (click)="checkRequest('/products')"
      ><span>Products</span></a
    >
  </li>

Modifying post once again to show console.log result from the (click)="checkRequest('/products');

CheckRequest method:

checkRequest(url) {
const newUrl = this.srvc.serverBase + url;
this.clickResponse = this.http.get(newUrl, { observe: "body" });
console.log(
  "newUrl: " +
    newUrl +
    " this.clickResponse: " +
    JSON.stringify(this.clickResponse)
);

}

From console.log when clicking the '/products' link:

newUrl: http://local.kronus:8001/products this.clickResponse: {"_isScalar":false,"source":{"_isScalar":false,"source":{"_isScalar":false,"source":{"_isScalar":true,"value":{"url":"http://local.kronus:8001/products","body":null,"reportProgress":false,"withCredentials":false,"responseType":"json","method":"GET","headers":{"normalizedNames":{},"lazyUpdate":null,"headers":{}},"params":{"updates":null,"cloneFrom":null,"encoder":{},"map":null},"urlWithParams":"http://local.kronus:8001/products"}},"operator":{"concurrent":1}},"operator":{}},"operator":{}}

The interceptor does not seem to be producing anything. Is there something that I missing to include the interceptor somehow?

Another update: after some more reading, I realized that I need to inject the interceptor into my login service:

private authInt: AuthInterceptor

Also, I added to the login service:

clickResponse: Observable<any>;

I moved the checkRequest method to the login service:

checkRequest(url) {
  const newUrl = this.appBase + url;
  return (this.clickResponse = this.http.get(newUrl, { observe: "body" }));
}

In the navbar component, I changed the method to the following calling the service's checkRequest:

checkRequest(url) {
this.srvc.checkRequest(url).subscribe(data => {
  console.log("after clicking login: ", data);
});
}

Here is the 404 error in the console:

    HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4200/login", ok: false, …}
error: "<!DOCTYPE html>↵<html lang="en">↵<head>↵<meta charset="utf-8">↵<title>Error</title>↵</head>↵<body>↵<pre>Cannot GET /login</pre>↵</body>↵</html>↵"
headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure response for http://localhost:4200/login: 404 Not Found"
name: "HttpErrorResponse"
ok: false
status: 404
statusText: "Not Found"
url: "http://localhost:4200/login"

How is it possible that /login is not found? I am literally on the login page. Is it because this checked before the app made the login page?

This is when I realized that I do not need to send '/login' but the uri to the api, which produced no errors, but no jwt in the headers either. I tried to do the same thing on admin/products page, which the following line seems to just return the payload coming from the api:

return (this.clickResponse = this.http.get(newUrl, { observe: "body" }));

Yet, I do not see anything in the headers section that jwt Authorization Bearer is passed. What am I missing?

Thanks in advance

1 Answers

The following import into app.module.ts is what I was missing:

import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { AuthInterceptor } from "./auth-interceptor";

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

Having HTTP_INTERCEPTORS in the providers array and using the auth interceptor that I made, automatically attaches to every http request - EVERY http request.

Also, at times I tried to inject the auth interceptor into the login services, when I eventually realized that I needed to inject the services into my interceptor, with the following call to this.srvc.getToken():

if (idToken) {
  const cloned = req.clone({
    headers: req.headers.set(
      "Authorization",
      "Bearer " + this.srvc.getToken()
    )
  });
  // console.log("Decoded jwt token: ", this.getDecodedAccessToken(idToken));
  return next.handle(cloned);
} else {
  return next.handle(req);
}

getToken from service:

public getToken(): string {
  return localStorage.getItem("jwt");
}

Now my Request Headers look like the following:

Provisional headers are shown
Accept: application/json, text/plain, */*
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6NDIwMCIsImF1ZCI6Imh0dHA6XC9cL2xvY2FsaG9zdDo0MjAwIiwiaWF0IjoxNTQ2OTU5NzUzLCJuYmYiOjE1NDY5NTg3NTMsImRhdGEiOnsibmFtZSI6InRhbUBrcm9udXNwcm9kdWN0aW9ucy5jb20iLCJwYXNzMSI6ImVhYmNkMTIzNDUifX0.GF1kFxdMe3Jd_paxu89Dve23ysguz4LGxXmGIDOz9Yc
Content-Type: application/json
Origin: http://localhost:4200
Referer: http://localhost:4200/login
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36

Since these tests are on my dev environment, then I do not to be running on https, but @paulsm4 is correct that all this needs to be done over https

BTW, thanks for your patience as I asked several questions

Related