Encoded JavaScript in ASP.NET MVC Core

To complete the Data URI saga, here is another technique that you may find interesting: this time, it’s about encoding JavaScript contents, so as to make them more difficult to tamper with.

Data URIs can be used for serving any content that would normally come from an external resource, such as a JavaScript/CSS file or an image. What we will do here is, using ASP.NET MVC Core’s Tag Helpers, we get hold of the JavaScript that is defined inside a SCRIPT tag and we turn into a Data URI. Pretty simple, yet the result is also pretty cool!

Show me the code, I hear you say:

[HtmlTargetElement("script")]
[HtmlTargetElement("script", Attributes = "asp-encrypt")]
[HtmlTargetElement("script", Attributes = "asp-src-include")]
[HtmlTargetElement("script", Attributes = "asp-src-exclude")]
[HtmlTargetElement("script", Attributes = "asp-fallback-src")]
[HtmlTargetElement("script", Attributes = "asp-fallback-src-include")]
[HtmlTargetElement("script", Attributes = "asp-fallback-src-exclude")]
[HtmlTargetElement("script", Attributes = "asp-fallback-test")]
[HtmlTargetElement("script", Attributes = "asp-append-version")]
public class InlineScriptTagHelper : ScriptTagHelper
{
    public InlineScriptTagHelper(ILogger<ScriptTagHelper> logger, IHostingEnvironment hostingEnvironment,
        IMemoryCache cache, IHtmlEncoder htmlEncoder, IJavaScriptStringEncoder javaScriptEncoder,
        IUrlHelper urlHelper) : base(logger, hostingEnvironment, cache, htmlEncoder, javaScriptEncoder, urlHelper)
    {
    }
 
    [HtmlAttributeName("asp-encrypt")]
    public bool Encrypt { get; set; }
 
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if ((this.Encrypt == true) && (string.IsNullOrWhiteSpace(this.Src) == true))
        {
            var content = output.GetChildContentAsync().GetAwaiter().GetResult().GetContent();
            var encryptedContent = Convert.ToBase64String(Encoding.ASCII.GetBytes(content));
            var script = $"data:text/javascript;base64,{encryptedContent}";
 
            output.Attributes.Add("src", script);
            output.Content.Clear();
        }
 
        base.Process(context, output);
    }
}

You can see that this tag helper inherits from ScriptTagHelper, which is the OOTB class that handles SCRIPT tags. Because of this inheritance, we need to add all the HtmlTargetElementAttributes, so that all the rules get applied properly. For this one, we need to add an extra rule, which forces the SCRIPT tag to have an asp-encrypt attribute.

So, say you have this in your Razor view:

<script asp-encrypt="true">
   1:  
   2:     window.alert('hello, world, from an encrypted script!');
   3:  
</script>

What you’ll end up with in the rendered page is this:

<script src="data:text/javascript;base64,DQoNCiAgICB3aW5kb3cuYWxlcnQoJ2hlbGxvLCB3b3JsZCwgZnJvbSBhbiBlbmNyeXB0ZWQgc2NyaXB0IScpOw0KDQo="></script>

Cool, wouldn’t you say? Of course, no JavaScript that runs on the client can ever be 100% secure, and you should always keep that in mind. But for some purposes, it does pretty well! Winking smile

                             

13 Comments

  • Nice post, I just have a question, pardon if the question is ridiculous but I am know on this. While the javascript is encrypted, how the client engine will execute that?

    Is this support by Chrome, Firefox??

    What about IE 11?

    Thanks in advance?

  • Hi, Julio!
    All of the above browsers will execute, just try it! ;-)

  • Great example of tag-helpers, but how is this encrypted? It just a Base64 string?

  • Hi, Oliver!
    Encrypted... as a Base-64 string, of course! :-)
    Not encrypted in the sense that it cannot be deciphered.

  • It is a common misconception that Base64 is encryption.

    It is not, however.

    The correct term is "encoding" - if the post was updated with this term it would be more clear what you're doing. There is absolutely zero tamper protection in this strategy, and it is misleading to say so.

    The article's example of a tag helper extending a standard one however is useful and interesting, so it's a pity it's headlined with a security misconception.

    One difference to simply inlining the script could be support for the "async" attribute without requiring a separate file. There could also be a difference in the way Content Security Policy handles it. These are both angles that make this example interesting without the misleading security claim.

    Thanks for the time and effort you've put into this useful series of articles.

  • This would be clearer if you said Encoded, not Encrypted.

  • I agree with Mark.

    Ricardo, If you were to add a debugger statement into the script block; does the browser decode the response and allow you to walk through the code?

  • Please rename this to encoded rather than encrypted, it may mislead others who might use this believing there is some sort of added security or obfuscation with this method.

  • Chaz: why don't you try?

  • Actually, is more like 33%. Your "other than" usage is the only one I mentioned!
    I didn't claim that is is good for anything else other than obfuscating the JavaScript, so I was really amazed by some of the comments!
    :-)

  • base64! 100% secure! not... 300%!

  • hi
    can use this method for ASP.NET MVC 5 or only use for ASP.NET MVC core?

  • David: only ASP.NET Core, as tag helpers are not available in pre-Core.

Add a Comment

As it will appear on the website

Not displayed

Your website