Rezo Kobaidze
Rezo Kobaidze

Reputation: 1

Angular You provided 'undefined' where a stream was expected

Please help. This error occurs when refreshAccessToken is caled from interceptor.But code excecution continues.

ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.

This is http Interceptor that calls refresh token if time has passed.

import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {AuthService} from "../../../services/auth.service";
import {SharedService} from "../../../services/shared.service";
import * as moment from "moment";

@Injectable()
export class HttpInterceptorInterceptor implements HttpInterceptor {

  constructor(private sharedService: SharedService, private authService: AuthService) {
  }

  putTokenInRequest(request: HttpRequest<any>, access_token: any): HttpRequest<any> {
    return request = request.clone({
      setHeaders: {
        Authorization: `Bearer ${access_token}`
      }
    })
  }

  excludeRequest(request: HttpRequest<any>): boolean {
    let request_to_exclude = [
      "/protocol/openid-connect/token"
    ]
    let exclude = false
    for (let req of request_to_exclude) {
      if (request.url.toString().includes(req)) {
        exclude = true
      }
    }
    return exclude;
  }


  intercept(request: HttpRequest<any>, next: HttpHandler) :any{

    if (this.excludeRequest(request) != true) {

        //before every request check if access token has expired
        let access_token_expire_date = localStorage.getItem("access_token_expire_date")

        //subtract 10 sc for safety
        if (moment().isAfter(moment(access_token_expire_date).subtract(10, 's'))) {
          console.log("access_token_expired")
          let refreshToken = localStorage.getItem("refresh_token")
          this.authService.refreshAccessToken(refreshToken).subscribe(response => {
            localStorage.setItem("refresh_token", response.refresh_token)
            localStorage.setItem("access_token", response.access_token)
            let access_token_expire_date = moment().add(response.expires_in, 's')
            let refresh_expire_date = moment().add(response.refresh_expires_in, 's')
            localStorage.setItem('access_token_expire_date', access_token_expire_date.toString())
            localStorage.setItem('refresh_expire_date', refresh_expire_date.toString())
            return next.handle(this.putTokenInRequest(request, response.access_token))
          })

        } else {
          //if access_token is active again.
          let access_token = localStorage.getItem('access_token')
          return next.handle(this.putTokenInRequest(request, access_token))
        }
    }
    else {
      //if request is excluded from interception just move on and don,t change it.
      return next.handle(request);
    }

  }
}

service

import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders, HttpParams} from "@angular/common/http";
import {environment} from "../../environments/environment";
import {Observable} from "rxjs";

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  keyckloakUrl
  baseUrl
  constructor(private http: HttpClient) {
    this.keyckloakUrl = environment.keyckloakUrl;
    this.baseUrl = environment.baseUrl;
  }

  getExccessToken(code: any): Observable<any> {

    const params = new HttpParams({
      fromObject: {
        client_id: 'authorization_code_grant_type',
        grant_type: 'authorization_code',
        code: code,
        redirect_uri:'http://localhost:4200/dashboard/token_redirect'
      }
    });

    const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/x-www-form-urlencoded',
      })
    };
    return this.http.post(`${this.keyckloakUrl}/realms/master/protocol/openid-connect/token`, params.toString(),httpOptions);
  }

  // logout():Observable<any>{
  //   return this.http.get(` http://localhost:8088/auth/realms/master/protocol/openid-connect/logout?redirect_uri=http://localhost:4200`);
  // }

refreshAccessToken(refreshToken:any):Observable<any>{
  // const params = new HttpParams({
  //   fromObject: {
  //     client_id: 'authorization_code_grant_type',
  //     grant_type: 'refresh_token',
  //     refresh_token: refreshToken,
  //   }
  // });
  // const httpOptions = {
  //   headers: new HttpHeaders({
  //     'Content-Type': 'application/x-www-form-urlencoded',
  //   })
  // };
  let body = new URLSearchParams();
  body.set('client_id', 'authorization_code_grant_type');
  body.set('grant_type', 'refresh_token');
  body.set('refresh_token', refreshToken);

  let options = {
    headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
  };
debugger
  return this.http.post<any>(`${this.keyckloakUrl}/realms/master/protocol/openid-connect/token`,  body.toString(),options);
}

  logout():Observable<any>{
    return this.http.post(`${this.baseUrl}/logout`,{})
}
}

Upvotes: 0

Views: 414

Answers (1)

Nam
Nam

Reputation: 569

This is what a proper interceptor interface look like according to Angular official documentation

interface HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
}

You're modifying the return type for intercept to any and lost all your types safety.

intercept(request: HttpRequest<any>, next: HttpHandler) :any

If you didn't do that, any decent code editor would highlight the problem for you.

And if you just take a look at this code block:

        if (moment().isAfter(moment(access_token_expire_date).subtract(10, 's'))) {
          console.log("access_token_expired")
          let refreshToken = localStorage.getItem("refresh_token")
          this.authService.refreshAccessToken(refreshToken).subscribe(response => {
            localStorage.setItem("refresh_token", response.refresh_token)
            localStorage.setItem("access_token", response.access_token)
            let access_token_expire_date = moment().add(response.expires_in, 's')
            let refresh_expire_date = moment().add(response.refresh_expires_in, 's')
            localStorage.setItem('access_token_expire_date', access_token_expire_date.toString())
            localStorage.setItem('refresh_expire_date', refresh_expire_date.toString())
            return next.handle(this.putTokenInRequest(request, response.access_token))
          })

You're not returning anything for the intercept function, the return next.handle(this.putTokenInRequest(request, response.access_token)) is inside the callback for your subscribe, therefor what you're returning is basically void or undefined

So all you have to do is return the stream instead:

...
    let refreshToken = localStorage.getItem('refresh_token');
    return this.authService.refreshAccessToken(refreshToken).pipe(
      map((response) => {
        localStorage.setItem('refresh_token', response.refresh_token);
        localStorage.setItem('access_token', response.access_token);
        let access_token_expire_date = moment().add(response.expires_in, 's');
        let refresh_expire_date = moment().add(
          response.refresh_expires_in,
          's'
        );
        localStorage.setItem(
          'access_token_expire_date',
          access_token_expire_date.toString()
        );
        localStorage.setItem(
          'refresh_expire_date',
          refresh_expire_date.toString()
        );
        return next.handle(
          this.putTokenInRequest(request, response.access_token)
        );
      })
    );

Upvotes: 1

Related Questions