Passing authorization header for images src to remote server in Ionic page
Asked Answered
C

4

44

I have an Ionic app where it fetches the data from remote server and displays it on Ionic html page.

The remote URL is like this:

http://foo.com/api/content/1

This will give me a JSON object of "content" and will be used further in the html page of Ionic app.

It is being used like this on html page inside Ionic app:

<div class="article-desc">
  <p [innerHtml]="myObject?.Body"></p>
</div>

"myObject" is the JSON object of response received from the server.

The "Body" field contains the HTML to be displayed in the paragraph. This "HTML" field is being returned from server only along with the entire "content" object.

"Body" field can have content like this:

<p>blah blah <img src="http://foo.com/image/1"/> blah blah <img src="http://foo.com/image/2"/>blah blah blah </p>

You can see from the above example that the images are there in that html.

I have no issue rendering the html from that field to Ionic Page.

I have one issue here that my images are not being rendered there.

Here is why..

My app is locked for Guest users so for each request I need to send an Authorization header in order to authenticate it and in this case all the images are not able to render because each image request will be treated as guest here for server.

Can you suggest a common place where all my images and other sources like there in html should pass through and can send authorization header along with it to server.

I already have the Authorization Token in local storage item.

My goal is to send authorization header to each external source (image here) present in that Body field when it renders in Ionic app.

Cheju answered 14/12, 2017 at 11:4 Comment(5)
You can implement the interceptor which monitors all the requests and you can attach header there, you will have to configure once and it will work for all the http calls made from your app. Have you considered writing this in your application?Gyasi
That seems a good idea. Do you have some reference from Ionic official documentation for doing the same?Cheju
well I can guide I think, I feel it will be same for ionic and angular after all it is angular at the end of the day, let me write an answer for you.Gyasi
Well, I am reading the following post medium.com/tableless/… in order to implement something like this. Waiting more good suggestions if you have..Cheju
Actually that link is enough, i can explain what interceptor is in my answer though. But the link you are referring looks enough as far as implementation is concerned.Gyasi
P
96

1) Create interceptor which sets authorization header

import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

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

        request = request.clone({
            setHeaders: {
                Authorization: `Bearer <your token>`
            }
        });

        return next.handle(request);
    }
}

Instead of <your token> you should inject your AuthService into this interceptor, for example this.authService.getToken(), which loads token from local storage.

2) Implement "secure" pipe which gets image as blob and creates an object url of that blob that can be used in the src attribute

import { Pipe, PipeTransform } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';

@Pipe({
    name: 'secure'
})
export class SecurePipe implements PipeTransform {

    constructor(private http: HttpClient, private sanitizer: DomSanitizer) { }

    transform(url): Observable<SafeUrl> {
        return this.http
            .get(url, { responseType: 'blob' })
            .map(val => this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(val)));
    }

}

3) Use it

<img [attr.src]="'your link' | secure | async" />
Peridotite answered 22/5, 2018 at 8:32 Comment(6)
Nice solution, thanks! I used your SecurePipe and inserted the authorization header in transform function before calling the http get. It worked too. It may be an alternative for those who does not want to implement an interceptor.Acidophil
Worked for me and is also applicable to the audio tag.Maynor
It is worth mentioning using an async pipe will cause the browser not to cache the image.Reade
@SDekov are you sure that your resource had Cache-Control?Bathypelagic
Any solution for [backgroundImage]="myImageUrl" ?Oxygenate
how to cache images? There are many images to need jwt auth and need good performance.Monochord
L
6

1st Option: Look for "URL signing"

The idea is that, when you use <img src="http://foo.com/image/1"> there is no way to pass the authorization headers. So instead, you make post request to your backend to ask for a temporary public link for the image and put this link as source of image.

Here is an example flow

  1. I need to show "http://foo.com/image/1"

  2. From the browser, make a post request to backend, let them know you are an authorized client (include the authorization header), and ask for a temporary url that will show the image publicly

  3. From the backend, generate a signed url that is valid for a limited time and does not require authorization headers to show the image.

  4. Use the temporary signed url you just received as src of the img tag.

2nd Option: Download the image and use blob URL

Answers to this question will tell you about it: Force HTTP interceptor in dynamic ngSrc request

Luker answered 14/12, 2017 at 11:31 Comment(10)
Shouldn't the interceptors like medium.com/tableless/… work? The requirement I am having seems to be a common one. I wonder how others are implementing it with their content having images pointing to their server.Cheju
That intercepts the requests that are done in the javascript part. It will not intercept the request that is done by the browser as a result of src of img tag, @ChejuLuker
And yes this is a common problem and this is one way to solve itLuker
As you can see I am just binding "<p [innerHtml]="myObject?.Body"></p>" i.e the Body to inner html there. Where should I intercept html and do a post request.Cheju
@Cheju there is no way to intercept a browser's get call to src of img tag. I updated my answer with a second option which would not need any modifications to the backend serviceLuker
Bases on your 2nd suggestion I guess I need to do this... parse the html, replace src tag of img with http-src tag with same url, Right?Cheju
@Raghav, parse the html, find the original url in src of img tag, download the image using javascript (including your auth header) and encode it as blob url, and replace the original url in src of img tag with this blob url. It should work this wayLuker
@Raghav, Forget what I just said. I didn't see that answer on the question I shared. Yes you are right. Just implement that http-src thingy as the answer suggests, and replace src with html-srcLuker
Before trying that.. Wouldn't it hamper the performance converting the image to blob? Will image still be coming from cache the next time?Cheju
@Cheju you are right, it won't be cached, on the other hand if you use the option 1 I suggested, catching could be valid only for the duration of the validity of the signed url, because next time the url will change, too. This is the trade off you have to decide, performance vs securityLuker
G
0

Here is how you write an interceptor,

  1. Need to extend a class called HttpInterceptor provided in angular 4/5.
  2. Override a method called intercept,

It will add the header in all your http request, this would ideally be the place where you would perhaps need to put sanitation logic, for an example if you wish to put just certain request with that header you can decide that in intercept method.

export class YourInterceptor implements HttpInterceptor{    
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
const NewRequest= req.clone({ headers: req.headers.set(‘Auth-Token’, ‘YourAuthToken’) });

return next.handle(NewRequest); }

After this you need to register this in your app.module.ts file in the manner given below,

import { YourInterceptor } from './Your-interceptor';

now go to the @NgModule section and do this in your provider array, it would be to the provider array as given below,

providers: [{provide: HTTP_INTERCEPTORS,useClass: YourInterceptor,multi: true}],

Now restart your app and whatever http call you make it will have a control inside which will intercept your code and you will be able to sail through.

Gyasi answered 14/12, 2017 at 11:43 Comment(0)
M
0

I solved this issue by utilizing cache storage.

  1. Add get/set/clear cache logic in service.
    import { Injectable } from '@angular/core';

    @Injectable({
      providedIn: 'root'
    })
    export class CacheService {
      public async getCache(key: string, link: string): Promise<Response | undefined> {
        const cache = await caches.open(key);
        return await cache.match(link);
      }
    
      public async setCache(key: string, link: string, response: Response): Promise<void> {
        const cache = await caches.open(key);
        await cache.put(link, response);
      }
    
      public async clearCache(key: string): Promise<void> {
        await caches.delete(key);
      }
    }
  1. Add new pipe for image fetching and saving blob data to cacheStorage.
    import { HttpClient } from '@angular/common/http';
    import { Pipe, PipeTransform } from '@angular/core';
    import { lastValueFrom } from 'rxjs';
    import { CacheService } from '../services/cache.service';
    
    @Pipe({ name: 'secure' })
    export class SecurePipe implements PipeTransform {
      constructor(
        private http: HttpClient,
        private cacheService: CacheService
      ) {}
    
      async transform(link: string): Promise<string> {
        const response = await this.cacheService.getCache('cache-key', link);
    
        if (response) {
          return URL.createObjectURL((await response.blob()) as Blob);
        }
    
        const serverResponse = await lastValueFrom(this.http.get(link, { responseType: 'blob' }));
        await this.cacheService.setCache('cache-key', link, new Response(serverResponse));
        return URL.createObjectURL(serverResponse);
      }
    }
  1. Apply pipe in template.
    <img [src]="yourImageLink | secure | async" />

Fetching images from cacheStorage is faster than from server. So frontend only needs to fetch from cacheStorage once loaded. Only one disadvantage is that it causes flicking issue. It worked in Angular 15 well. I hope this can be helpful!

Monochord answered 21/6, 2024 at 19:19 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.