diff --git a/login/resources/js/base64url.js b/login/resources/js/base64url.js
new file mode 100644
index 0000000..3d69bbd
--- /dev/null
+++ b/login/resources/js/base64url.js
@@ -0,0 +1,115 @@
+// for embedded scripts, quoted and modified from https://github.com/swansontec/rfc4648.js by William Swanson
+'use strict';
+var base64url = base64url || {};
+(function(base64url) {
+
+    function parse (string, encoding, opts = {}) {
+        // Build the character lookup table:
+        if (!encoding.codes) {
+              encoding.codes = {};
+              for (let i = 0; i < encoding.chars.length; ++i) {
+                  encoding.codes[encoding.chars[i]] = i;
+              }
+        }
+
+        // The string must have a whole number of bytes:
+        if (!opts.loose && (string.length * encoding.bits) & 7) {
+            throw new SyntaxError('Invalid padding');
+        }
+
+        // Count the padding bytes:
+        let end = string.length;
+        while (string[end - 1] === '=') {
+            --end;
+
+            // If we get a whole number of bytes, there is too much padding:
+            if (!opts.loose && !(((string.length - end) * encoding.bits) & 7)) {
+                throw new SyntaxError('Invalid padding');
+            }
+        }
+
+        // Allocate the output:
+        const out = new (opts.out || Uint8Array)(((end * encoding.bits) / 8) | 0);
+
+        // Parse the data:
+        let bits = 0; // Number of bits currently in the buffer
+        let buffer = 0; // Bits waiting to be written out, MSB first
+        let written = 0; // Next byte to write
+        for (let i = 0; i < end; ++i) {
+            // Read one character from the string:
+            const value = encoding.codes[string[i]];
+            if (value === void 0) {
+                throw new SyntaxError('Invalid character ' + string[i]);
+            }
+
+            // Append the bits to the buffer:
+            buffer = (buffer << encoding.bits) | value;
+            bits += encoding.bits;
+
+            // Write out some bits if the buffer has a byte's worth:
+            if (bits >= 8) {
+                bits -= 8;
+                out[written++] = 0xff & (buffer >> bits);
+            }
+        }
+
+        // Verify that we have received just enough bits:
+        if (bits >= encoding.bits || 0xff & (buffer << (8 - bits))) {
+            throw new SyntaxError('Unexpected end of data');
+        }
+
+        return out
+    }
+
+    function stringify (data, encoding, opts = {}) {
+        const { pad = true } = opts;
+        const mask = (1 << encoding.bits) - 1;
+        let out = '';
+
+        let bits = 0; // Number of bits currently in the buffer
+        let buffer = 0; // Bits waiting to be written out, MSB first
+        for (let i = 0; i < data.length; ++i) {
+            // Slurp data into the buffer:
+            buffer = (buffer << 8) | (0xff & data[i]);
+            bits += 8;
+
+            // Write out as much as we can:
+            while (bits > encoding.bits) {
+                bits -= encoding.bits;
+                out += encoding.chars[mask & (buffer >> bits)];
+            }
+        }
+
+        // Partial character:
+        if (bits) {
+            out += encoding.chars[mask & (buffer << (encoding.bits - bits))];
+        }
+
+        // Add padding characters until we hit a byte boundary:
+        if (pad) {
+            while ((out.length * encoding.bits) & 7) {
+                out += '=';
+            }
+        }
+
+        return out
+    }
+
+    const encoding = {
+        chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
+        bits: 6
+    }
+
+    base64url.decode = function (string, opts) {
+        return parse(string, encoding, opts);
+    }
+
+    base64url.encode = function (data, opts) {
+        return stringify(data, encoding, opts)
+    }
+
+    return base64url;
+}(base64url));
+
+
+