Add JWT token to img src GET request

Hi Syncfusion Team,

We would like to use RTE on one of our pages but we have a problem with getting images.

Every time RTE adds image into text, the <img> element is created with apriopriate src value.
In our case this src value is Django REST endpoint that returns image as blob.

For this moment everything works well:
1. Selected image is succesfuly uploaded on server.
2. After upload, new image is inserted into RTE.

However any of those inserted image cannot be downloaded and displayed in RTE. The reason is lack of JWT token in GET when requesting for image.

Normally we would add JWT by adding something like secure pipe that will generate GET request with JWT token added authomaticaly by Authorization Interceptor and return observable. Like in this example: https://stackoverflow.com/questions/46563607/angular-4-image-async-with-bearer-headers

But in this case we need to store RTE value in database with img src as the endpoint address not 64 base string.

Is it possible to add JWT token to the GET request created when img element trying to download file?


17 Replies 1 reply marked as answer

IS Indrajith Srinivasan Syncfusion Team May 13, 2021 01:54 PM UTC

Hi Ignacy, 
 
Greetings from syncfusion support, 
 
We have validated your reported query “Is it possible to add JWT token to the GET request”. You can achieve your requirement by using the ‘imageUploading’ event in the Rich Text Editor, where the Authorization headers can be set. We have also prepared a sample, for your reference,  
  
Code Snippet:  
  
<ejs-richtexteditor id="defaultRTE" [insertImageSettings]="insertImageSettings" (imageUploading)="onImageSelected($event)">  
</ejs-richtexteditor>  
  
public onImageSelected(args: any): void {  
  // Setting Authorization Header  
  args.currentRequest.setRequestHeader(  
    "Authorization",  
    "Bearer ${token key to be passed}"  
  );  
  // You can also add additional parameters as key-value pair  
  args.customFormData = [{ name: "Syncfusion INC" }];  
}  
  
 
Can you please check the above sample, and let us know if it meets your requirements ?  
 
Regards, 
Indrajith 


Marked as answer

IM Ignacy Mielniczek May 14, 2021 08:19 AM UTC

Hi Indrajith,

We are using already the solution you described below to upload files to the server. The request that is send during upload is POST not GET. Anyway this is not our problem.

Our issue occurs after uploading file.

After the file is successfully uploaded user clicks Insert button. It cause the RTE component inserts <img> element into RTE content. The code would look something like:

<p>Some text</p> <img src="http://your_image_url" ><p>Some other text</p>

To display such an image, browser will send GET request to path declared in src attribute. Unfortunately this GET request will not be caught by Angular Authorization Interceptor and will not have JWT token. In result no image will be displayed.


Is it possible to add token to this request?





IS Indrajith Srinivasan Syncfusion Team May 17, 2021 11:12 AM UTC

Hi Iganacy, 
 
Good day to you, 
 
We have validated your reported query. We suspect your requirement is like the below case, for adding the bearer token with the image get request ?  
 
 
Can you please try the below suggestion, for changing the image name (args.file.name) in the imageUploadSuccess event, which will change the corresponding image name src attribute path. 
  
 
 
Please let us know if the solution helps, 
 
Regards, 
Indrajith 



IM Ignacy Mielniczek June 1, 2021 09:39 AM UTC

Hi Indrajith,


I am afraid that changing file name will not help us. Even if we want to add token to the file name it is not possible, because we would like to store the whole RTE content in database. And storing those data with token is worthless as it will not be valid after time.




IS Indrajith Srinivasan Syncfusion Team June 2, 2021 12:44 PM UTC

Hi Ignacy, 
 
Good day to you, 
 
We have further validated on your reported query, you can add the token for the images src attribute in the actionComplete event of the Rich Text Editor when the image is added in the Rich Text Editor. Later when downloading, you can access based on the bearer token added. Check the below shared sample for reference. 
 
 
public onComplete(args) { 
    if (args.requestType === 'Image'){ 
      this.imageElement = args.elements[0]; 
      this.imageElement.src = this.imageElement.src + '?access_token=mF_9.B5f-4.1JqM'; 
    } 
  } 
 
 
 
If you want to add the token dynamically with the button, click action, you can pass the token for the images with the get request using the XMLHttpRequest() adding the token for the image src attribute. Check the above shared example for reference. 
 
 
public getImage() { 
    var request = new XMLHttpRequest(); 
    this.imageElement.src =  this.imageElement.src + '?access_token=mF_9.B5f-4.1JqM'; 
    request.open('GET', this.imageElement.src, true); 
    request.setRequestHeader('Authorization', 'Bearer ' + 'test_123'); 
     
    request.send(); 
  } 
 
 
Please let us know if the above solutions help, 
 
Regards, 
Indrajith 



HB Herval Bernice Nganya Nana replied to Indrajith Srinivasan June 29, 2021 07:11 PM UTC

Hi Indrajith,

I have a problem slightly different from what was discussed above.


I have an api to upload files which takes two parameters:

  • a json object containing the meta data. The object is called metadata.
  • and a file stream which contains the file itself. This object is called file.

With the help of the previous comments, I could provide the metadata. But now, my problem is to change the name of the file stream to 'file', since so far, the richtext module calls that parameter UploadFiles.

Finally, I would like to please how do I get the json response id to display the file, since the image that I will display in the text will be accessible via the response id.

I thank you in advance


IS Indrajith Srinivasan Syncfusion Team June 30, 2021 09:06 AM UTC

Hi Herval, 
 
Good day to you, 
 
We have validated your reported query “I would like to please how do I get the json response id to display the file, since the image that I will display in the text will be accessible via the response id”. You can send the response in the headers, and change the image source URL in the imageUploadSuccess event argument by using the args.file.name to display the image in the editor with the sent response in headers. Check the below code blocks and documentation for reference. 
  
  
public onImageUploadSuccess = (args: any) => {  
    if (args.e.currentTarget.getResponseHeader('name') != null) {  
        // File name returned from the server, if added in the header  
        args.file.name = args.e.currentTarget.getResponseHeader('name');  
    }  
 
  
Server  
  
// Adding the modified headers in the response  
Response.Headers.Add("name", imageFile);  
  
  
 
 
Please let us know if the solution helps, 
 
Regards, 
Indrajith


MU Muamer replied to Ignacy Mielniczek July 30, 2023 12:03 PM UTC

We have the same issue.

For the moment we will implement random guid like resource id as part of url.

Considering it is behind TLS, that part of url is encoded and we'll have to put up with it for the moment.


Yet, we would also very much prefer to implement proper security via header with jwt.


Considering there are already examples of custom upload,

( Upload image directly to AWS S3 using pre-signed URL | Angular - EJ 2 Forums | Syncfusion)

and that there is event to sanitize html, Syncfusion please consider either one:

  1. publishing sanitization event of GET request from rte component.
    or
  2. utilizing existing authorization interception in Angular


Thanks,




VY Vinothkumar Yuvaraj Syncfusion Team August 3, 2023 02:46 PM UTC

Hi Muamer,


We have validated your reported query "publishing sanitization event of GET request from rte component or utilizing existing authorization interception in Angular". You can achieve your requirement by using the ‘imageUploading’ event in the Rich Text Editor. In that event, authorization headers can be set. We have also prepared a sample for your reference.


Code Snippet:

<ejs-richtexteditor id="defaultRTE" [insertImageSettings]="insertImageSettings" (imageUploading)="onImageSelected($event)">  

</ejs-richtexteditor>  

public onImageSelected(args: any): void {  

  // Setting Authorization Header  

  args.currentRequest.setRequestHeader(  

    "Authorization",  

    "Bearer ${token key to be passed}"  

  );  

  // You can also add additional parameters as key-value pair  

  args.customFormData = [{ name: "Syncfusion INC" }];  

}  


Sample: https://stackblitz.com/edit/angular-xcb73d-p9hqfy?file=src%2Fapp.component.html,src%2Fapp.component.ts


Can you please check the above sample, and let us know if it meets your requirements?



MU Muamer August 4, 2023 02:51 AM UTC

Hello Vinothkumar Yuvaraj,


Thanks for your response.

We are already using this for upload. The issue is not with the upload but reading i.e. downloading that image later on when rich text editor is rendering its content.

Consider the following minimalistic sample:


Source code of sample is:

<p>Sample text</p>
<p>
<img style="opacity: 1; max-width: 1343px;" src="http://orka.lan/v2/resources/download/3a2e3c54-edd7-480e-86b1-4acf906c2fe4" alt="3a2e3c54-edd7-480e-86b1-4acf906c2fe4" class="e-imgbreak e-rte-image">
</p>

So, image tag of provided sample contains a link to actual image resource. If given resource is protected with jwt, there is no way we can affect implicit load that rich text editor is doing.

If protected, image would be unavailable.





VY Vinothkumar Yuvaraj Syncfusion Team August 7, 2023 02:33 PM UTC

Hi Muamer,


We hope the following solution meets your requirements.


We have made a sample to meet your requirements. In this sample, we have removed the image element src based on the authorization token. Please take a look at the following code and sample.


<ejs-richtexteditor

      [insertImageSettings]="insertImageSettings"

      (imageUploading)="onImageSelected($event)"

      (actionComplete)='complete($event)'

      (imageUploadSuccess)='imageUploadSuccess($event)' 

    >

    </ejs-richtexteditor>



export class AppComponent {

  public accessTokan = [];

  public tokenName = 1;

  public imageToken = false;

  private insertImageSettingsObject = {

    saveUrl: http://localhost:62869/api/richtexteditor/SaveFile,

    path: http://localhost:62869/Images/

  };

 

  public onImageSelected(argsany): void {

    // Setting Authorization Header

    args.currentRequest.setRequestHeader(

      "Authorization",

      "Bearer ${token}" + this.tokenName

    );

    this.accessTokan.push("Bearer ${token}" + this.tokenName)

    this.tokenName++;

  }

  public complete(args): void {

    if (args.elements[0] != null && this.imageToken) {

      if(args.elements[0].tagName == 'IMG'){

        args.elements[0].src = "";

        this.imageToken = false;

      }

    }

  }

  public imageUploadSuccess = (argsany=> {

    if (args.e.currentTarget.getResponseHeader("url") != null) {

      var token = args.e.currentTarget.getResponseHeader("url");

      if(this.accessTokan.indexOf(token) > -1){

        this.imageToken = true;

      }

    }

  }

}


Server

public ActionResult SaveFile(IList<IFormFile> UploadFiles)

{

try

{

                HttpContext.Request.Headers.TryGetValue("Authorization", out StringValues HeaderContent);

 

                foreach (IFormFile file in UploadFiles)

                {

                    if (UploadFiles != null)

                    {

//

string filename = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');

                        filename = _hostingEnvironment.WebRootPath + \\Images + $@"\{filename}";

                        if (!System.IO.File.Exists(filename))

                        {

                            using (FileStream fs = System.IO.File.Create(filename))

                            {

file.CopyTo(fs);

                                fs.Flush();

                            }

                           

                        }

                        // Modified image URL to the response headers

                        Response.StatusCode = 200;

                  Response.Headers.Add("url", HeaderContent.ToString());

 

                        Response.ContentType = "application/json; charset=utf-8";

                    }

                }

}

catch (Exception e)

{

                Response.Clear();

                Response.ContentType = "application/json; charset=utf-8";

                Response.StatusCode = 204;

Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = "No Content";

                Response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = e.Message;

}

return Content("");

        }



Sample : https://stackblitz.com/edit/angular-xcb73d-heqgcr?file=src%2Fapp.component.html,src%2Fapp.component.ts


If it does not meet your requirement, can you please share more details about your requirement? This will help us provide a better solution from our end.




VY Vinothkumar Yuvaraj Syncfusion Team August 7, 2023 03:53 PM UTC

Hi Muamer,

Additionally, we would like to inform you that by loading the initial image on the server side, we can protect the image on the server. However, we can't show the image from the server.



PS Philipp Schmidbauer May 3, 2024 04:36 PM UTC

It seems like this issue is still not fixed or correctly adressed because I think the use case was not properly understood.


An use case is: I write an article with your rte editor and I only want this article to be available to 2 out of 10 employees. Then I upload images in that article (which hold highly sensitive information). The article with the RTE cannot be accessed by my employees, but someone sent around the URL with the image that has to be public for the RTE as it must have a URL to an image that is public without authorization and everybody can now see this image. Hence, we need to ensure that the URL with the image can only be accessed through authorization. Then the RTE doesn't display it properly anymore.


How can we add images to an RTE and tell the RTE that it needs to check the JWT token before displaying the image because someone unauthorized might be staring at the RTE.


Thanks



VJ Vinitha Jeyakumar Syncfusion Team May 6, 2024 11:21 AM UTC

Hi Philipp Schmidbauer,


As per your requirement to check the JWT token before displaying the image in the RichTextEditor can be achieved by using the actionComplete event as we updated earlier, where you can add a condition like below,

code snippet:

 public actionComplete (args): void {

    if (args.elements[0] != null && !this.imageToken) {

      if(args.elements[0].tagName == 'IMG'){

// here you can remove the src of the image, if they don't have the image token

        args.elements[0].src = "";

        this.imageToken = false;

      }

    }

  }


Regards,
Vinitha


PS Philipp Schmidbauer May 6, 2024 09:01 PM UTC

Since nobody talked about a workaround yet, I will add one here for everyone who is struggling with this like me. I hope that syncfusion will provide a proper solution for this though as it should be part of a RTE in my opinion.

After the RTE is created, my workaround loops over all <img> elements, then loading the images with a JWT token from the backend and lastly replaces the image with a generated blob in a URL that is stored on localhost in the browser. I also ensured to cleanup the rendered URLs as it could lead to potential memory issues:

Here is the RTE editor in HTML with the created event:

<ejs-richtexteditor id='my-rte' #myRTE
(actionBegin)="actionBegin($event)"
(created)="replaceImgSources($event)" ... >
</ejs-richtexteditor>

<ejs-uploader #defaultupload id='defaultfileupload' [asyncSettings]='path' [dropArea]='dropElement'
(selected)='onFileSelect($event)'
(success)='onUploadSuccess($event)' (failure)="retry($event)" (uploading)="addHeaders($event)" (removing)="addHeaders($event)"></ejs-uploader>


Here is the function to replace the images in Angular (Typescript). The URL to load the image in my case is http://<backendUrl>/api/download/:id, hence I split() and pop().


async replaceImgSources(args) {
try {
const elements: Element[] = Array.from(document.getElementsByClassName("rte-img"));
for (const el of elements) {
const src = el.getAttribute('src');
if (src) {
const id = src.split('/').pop();

const res = await new Promise<HttpResponse<ArrayBuffer> | { error: any; type: string; }>((resolve) => {
this.fileUploadService.download(id).subscribe({
next: (response: HttpResponse<ArrayBuffer>) => resolve(response),
error: (err) => resolve({ error: err, type: '' }) // Handling errors
});
});

if ('error' in res) {
this.snackBar.open('Error downloading image: ' + res.error, null, { duration: 5000, horizontalPosition: 'left' });
} else {
const blob = new Blob([res.body], { type: res.headers.get('Content-Type') });
const imageUrl = URL.createObjectURL(blob);
this.blobURLs.push({
blobUrl: imageUrl, backendUrl: `http://${environment.serverUrl}/api/image/${id}`
});
el.setAttribute('src', imageUrl);
}
}
}
} catch (error) {
console.error("Error replacing image sources:", error);
}
}


You might have noticed that I stored the blob url and the backend url both in the blobURLs

I did this because of two reasons. First, when destroying the component the urls should be cleaned up to avoid any memory issues:

ngOnDestroy(): void {
// Call a function to revoke all blob URLs when the component is destroyed
this.revokeAllBlobURLs();
}

blobURLs: { backendUrl: string, blobUrl: string }[] = [];

revokeAllBlobURLs(): void {
// Revoke all blob URLs created during the component's lifetime

this.blobURLs.forEach(blobUrlObject => {
URL.revokeObjectURL(blobUrlObject.blobUrl);
});

this.blobURLs = null; // Clear the array
}


Second, I also need to change the url back when saving the document to the database:

save() {
let htmlString = this.html;
this.blobURLs.forEach(blobUrlObject => {
// String to search for
let searchString = `src="${blobUrlObject.blobUrl}"`;

// New URL to replace with
let newUrl = `src="${blobUrlObject.backendUrl}"`;

// Replace the old URL with the new one
let updatedHtmlString = htmlString.replace(searchString, newUrl);

//store to database...
});
}


Then here is the code for adding an image:

public onUploadSuccess = (args: any) => {
this.retryCount = 0;
(this.rteObj.contentModule.getEditPanel() as HTMLElement)?.focus();
this.range = this.selection.getRange(document);
this.saveSelection = this.selection.save(this.range, document);
var fileUrl = "/app/download?id=" + this.selectedFileId;
if (this.rteObj.formatter.getUndoRedoStack().length === 0) {
this.rteObj.formatter.saveData();
}
this.saveSelection.restore();
if([ 'image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/svg+xml' ].includes(this.selectedFileType)) {


this.fileUploadService.download(this.selectedFileId).subscribe((res: HttpResponse<any>) => {
if (res.status !== 200) {
this.snackBar.open(res.statusText, null, { duration: 5000, horizontalPosition: 'left' });
} else {
const blob = new Blob([res.body], { type: res.headers.get('Content-Type') });
const imageUrl = URL.createObjectURL(blob);
this.blobURLs.push({
blobUrl: imageUrl, backendUrl: `http://${environment.serverUrl}/api/image/${this.selectedFileId}`
});
this.rteObj.executeCommand('insertImage', { url: imageUrl, cssClass: 'rte-img' });
}
});


} else {
this.rteObj.executeCommand('createLink', { url: fileUrl, text: this.storedFileName, selection: this.saveSelection, target: "_blank" });
}
this.rteObj.formatter.saveData();
this.rteObj.formatter.enableUndo(this.rteObj);
this.uploadObj.clearAll();
this.storedFileName = '';
this.selectedFileId = '';
this.closeDialog();
}


Hope this solution helps others.



PS Philipp Schmidbauer replied to Vinitha Jeyakumar May 6, 2024 09:09 PM UTC

Hey Vinitha, 


just saw your response. Thanks for that, but that still doesn't help. What your solution fixes is that the image is not in the RTE anymore if you don't have a token. However, the image still has to be publicly available in the internet to be showed in the RTE in case you have a token. And for many users of the RTE that is a no go. Hence, my solution hopefully helps a couple people in the future until you guys might fix this. Let me know if you'll consider that or if you have any questions.



VJ Vinitha Jeyakumar Syncfusion Team June 18, 2024 12:57 PM UTC

Hi Philipp Schmidbauer,

Thanks for sharing the solution for your requirements. We would like to inform you that this is not an issue with RichTextEditor and your requirement with image uploading can be achieved in application end by passing tokens like the way you tried.

Regards,
Vinitha

Loader.
Up arrow icon