0

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`,{})
}
}

1 Answer 1

1

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)
        );
      })
    );
Sign up to request clarification or add additional context in comments.

1 Comment

And one more thing if somebody will want to use this code use switchMap not map. To handle request properly after getting access token

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.