Paul
Paul

Reputation: 419

Keycloak for Angular not sending bearer authentication for Images

I am running an Angular application with a Spring Boot MVC API in combination with Keycloak. I configured Angular following the instruction found under https://www.npmjs.com/package/keycloak-angular and it generally works fine.

In my Angular components I use the img tag to include Images, which are served by my Spring Boot application

<img *ngIf="id" src="api/v1/profile/image/{{id}}">

The MVC endpoint looks like this :

@GetMapping(value = "profile/image/{id}")
    public @ResponseBody byte[] getProfileImage(@AuthenticationPrincipal Jwt jwt, @PathVariable String id)
            throws IOException {
            ...
            }

The problem is that I receive a 401 Response Code on the first load of the image.

Here are my key findings:

Can anybody point out, what I can do? Can keycloak be configured in a way, that the load of Images will contain the bearer authetication header?

Upvotes: 4

Views: 2593

Answers (3)

Catalin Patrut
Catalin Patrut

Reputation: 31

The [src] attribute in HTML is used to specify the source URL for an image, video, audio file, or any other external resource that is included in the web page, it's rather part of the HTML standard than angular.

A way to tweak it would be to use data binding. Ex:

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.css']
})
export class MyComponent {
  imageUrl: string;

  constructor(private http: HttpClient, private sanitizer: DomSanitizer) {
    this.http.get('https://example.com/image.png', { responseType: 'blob' })
      .subscribe(blob => {
        const url = URL.createObjectURL(blob);
        this.imageUrl = this.sanitizer.bypassSecurityTrustUrl(url);
      });
  }
}

// In your template
<img [src]="imageUrl" />

Upvotes: 0

Amirhossein Mehrvarzi
Amirhossein Mehrvarzi

Reputation: 18974

Angular is powerful enough to satisfy your server requirements. You can force the img tag to request the image via an HttpClient.

So start with Writing a pipe that accepts an image url and performs the HTTP request while putting on the Authorization header.

The pipe can look like this:

@Pipe({
  name: 'authImage'
})
export class AuthImagePipe implements PipeTransform {

  constructor(
    private http: HttpClient,
    private auth: AuthService, // your service that provides the authorization token
  ) {}

  async transform(src: string): Promise<string> {
    const token = this.auth.getToken();
    const headers = new HttpHeaders({'Authorization': `Bearer ${token}`});
    const imageBlob = await this.http.get(src, {headers, responseType: 'blob'}).toPromise();
  }

}

You also need to Transform the blob response to a base64 string so it can be passed to the src attribute. Then put the base64 string into a Promise, so that it can be passed to the async pipe in its turn. Here we provide them plus error handling (server errors like 40X, 50X) using fallback image altogether:

async transform(src: string): Promise<string> {
  const token = this.auth.getToken();
  const headers = new HttpHeaders({'Authorization': `Bearer ${token}`});
  try {
    const imageBlob = await this.http.get(src, {headers, responseType: 'blob'}).toPromise();
    const reader = new FileReader();
    return new Promise((resolve, reject) => {
      reader.onloadend = () => resolve(reader.result as string);
      reader.readAsDataURL(imageBlob);
    });
  } catch {
    return 'assets/fallback.png';
  }
}

In this way, you can use sth like this:

<img [src]="'api/v1/profile/image/' | authImage | async"/>

You can also write an additional component for the new version of img tag like this :

@Component({
  selector: 'image',
  template: '<img [src]="src | authImage | async" [alt]="alt"/>',
})
export class ImageComponent {

  @Input() src: string;
  @Input() alt = '';
}

And then use it like this :

<image src="api/v1/profile/image/"></image>

Upvotes: 3

Jan Garaj
Jan Garaj

Reputation: 28714

<img *ngIf="id" src="api/v1/profile/image/{{id}}">

This kind of image is loaded by the browser and not by HttpClient. And HttpClient is already configured to inject Authorization header in your case, not the browser so that is a root cause of the issue.

You may load image content from the URL api/v1/profile/image/{{id}} with HttpClient and then do a inlining of that image content into HTML with base64 encoding:

<img src="data:<correct content type>;base64, <base 64 data of the image content>" />

e.g.:
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
    AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
        9TXL0Y4OHwAAAABJRU5ErkJggg==" />

It is good solution for small images (this approach is used for social icons usually).

Or you can save authorization into cookie in the same format as your service provider is expecting before you create image). Then browser will send that authorization cookie in the each request. Of course there can be a problem with cross domain cookies.

Upvotes: 5

Related Questions