Reputation: 419
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:
HttpClient
)set-cookie
response HeaderCan 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
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
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
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