1

I want to use Webp format for user's uploaded images in order to reduce bandwidth usage but I'm not really sure how to it with authorisation needed.

I want to limit access and control who can see a user's images, I need to pass a token to my API. The problem is that sharing the url of the image can result in persons who do not have to credentials to see the picture if it's done like that :

<img src={`${urlApi}/images/${imageId}/${token}`}/>

I've seen how to set header for image tag but I've no idea how to do it with NextJs (*) because of the nature of build in loader which return an url and would face similar credentials problems

What could be way to do this ? Any suggestions ?

(*) Moreover, from what i've seen and the console warnings I had Next does not like to use img tag

JeanJacquesGourdin
  • 1,496
  • 5
  • 25
  • 2: you don't need database for authorization. It happens in the nodejs app regardless of where the image is stored. 1: how do you plan to delete cached images when access is revoked? – Alex Blex Jun 23 '22 at 12:28
  • @AlexBlex 2-I dont know what you understood, I never asked anything like that. 1- I do it by suppressing the said part of the state by a command send by websocket. – JeanJacquesGourdin Jun 23 '22 at 12:51

2 Answers2

2

You can do this a couple of ways and you may not need the token if the token is added based on the logged in status of a user.

Method 1: Use a custom Express server to host your application. Put the route handler for images before the nextjs handler and validate your token or validate your session before returning the image.

Method 2: put your nextjs app behind nginx or apache and proxy the /images folder to an app that validates the token (JWT signed by a private key in your nextjs app?) and proxy / to the nextjs app.

In any case add cache-control headers so that the image is not cached and may only be retrieved unless the session is valid. Note that this is a best effort since once you have released the image once you have no actual control of it.

Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate

Note the Next/loader is meant as a way to manipulate which public image url is supplied for a given screen size. You might use a custom loader to add the token but it doesn't have utility for this use case other than that.

bknights
  • 14,408
  • 2
  • 18
  • 31
  • Thanks a lot for the leads. I think I understand what you mean. The think is that I want to avoid SSR (Method 1) to reduce next server bandwidth and that my images are saved on database accessible via nodejs api so there is no real /images folder. I think I will like you said at the end, namely build a custom component / image loader with the method I linked to set a custom header. Thanks again ! – JeanJacquesGourdin Jul 15 '22 at 08:58
  • The image loader comment contained a 'but'. To clarify a custom loader is client side only and will not do what you want. – bknights Jul 15 '22 at 15:08
  • I think there is a misunderstanding indeed. I think I did not express myself correctly, please tell me if I dont make sense : "The client has a token and gives it as a header when it requests a nodejs api for a webp image stored on a database, the api checks the credentials and returns the image as a blob if needs are met. It is the optimal way to reduce bandwidth usage on nextjs server and nodejs api." – JeanJacquesGourdin Jul 15 '22 at 16:45
  • No. This does not make sense. Keeping your images out of your nextjs bundle does make sense. Proxying image requests with some mechanism does make sense. Storing images in a database and looking them up makes no sense for the stated use. This will always be slower than pulling them from a filesystem. Really the easiest thing is to use nginx with proxying configurations. – bknights Jul 15 '22 at 20:03
  • I see, I did not explicit the fact that images were uploaded by users. I really don't want to be directly in touch with the file system and especially don't want to make the replication myself. I edited the question to be in line with this requirement. – JeanJacquesGourdin Jul 16 '22 at 18:43
  • How about using the Next JS's own API routes to set necessary Auth Headers/Credentials and then add that route to the Image's src instead ? – Sushant Rajbanshi May 02 '23 at 07:15
2

I found a solution to my own problem, it may be a little overengineered but it works pretty well imo.

So I've made a custom Image tag that can be called this way :

<ProfileImage urlAPI={urlAPI} url={`/profile/${pictureId}`} token={token}/>

And works like so :

import React, {useState, useEffect} from 'react';
import Image from 'next/image'
export default function ProfileImage({urlAPI, url, token}) {

    const [image, setImage] = useState(false);

    useEffect(() => {
        (async () => {
            //Get if url already exists in local storage
            let cached = window.localStorage.getItem(url);
            if(cached){
                setImage(cached);
            //If not, get it from API
            } else {
                const response = await fetch(urlAPI+url, {
                    headers: {
                        'Method': 'GET',
                        'Authorization': `${token}`
                    }
                });
                //Extract image from response body
                let base64Image = await response.json();
                let imageObject = await fetch(`data:image/webp;base64,${base64Image}`);
                imageObject = await imageObject.blob();

                //Create an Url that stores the image
                const objectURL = URL.createObjectURL(imageObject);

                //Save in local storage and component state
                setImage(objectURL);
                window.localStorage.setItem(url, objectURL);
            }
        })();
       () => {
         // clear memory if needed
         URL.revokeObjectURL(image);
         window.localStorage.removeItem(src);
       }
    }, [])

    return (
        <>
        {   image ?
            <Image src={image} alt="profile" className="rounded-full" width={90} height={90}/>
            :
            <div className=""></div>
        }
        </>
    )
}

The thing i did not know is that the browser can have urls generated on the go to store and cache images, so I've used this feature because the component kept asking api for images and recreated local urls every time the component was rendered.

I will upgrade this component and make cache invalidation.

JeanJacquesGourdin
  • 1,496
  • 5
  • 25