-
Notifications
You must be signed in to change notification settings - Fork 363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Polyfills needed for IE 11? #113
Comments
I am getting similar issue in IE
seems to be coming from |
yep, I mentioned this in another ticket as well, I think a fix for at least Crypto, will be in the next release #98 |
Does anyone have a suggested "quick fix" for the Unhandled promise rejection ReferenceError: 'crypto' is undefined problem?? We're getting beat up bad right now as a lot of our users on using windows 11 unfortunately. Thanks. |
@izzmeifter This is the polyfill they use in the legacy build: Lines 3 to 5 in 070505c
|
This solves the crypto issue but immediately exposes another issue for me. it appears that |
@ed-sparkes I think this should be solved by #98 (ES5 compatibility) which is in the next release as well. Eagerly awaiting the release now... :) |
@PapaMufflon It looks like #98 has already been released in v1.1.1: https://github.com/auth0/auth0-spa-js/blob/master/CHANGELOG.md |
@jstayton @ed-sparkes You are correct, my bad. It still does not work on IE11. |
Finally got it working. I had to polyfill |
@PapaMufflon can you share your code for this polyfill thanks |
Sure, I'm using Angular and am very sure that there are better solutions. In main.ts: // IE11 fetch polyfill
import 'unfetch/polyfill/index';
auth0Ie11Polyfill();
// standard angular-stuff
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
// auth0-IE11 polyfill begins here
function auth0Ie11Polyfill() {
webcryptoShim(window);
if (!Uint8Array.prototype.slice) {
Object.defineProperty(Uint8Array.prototype, 'slice', {
value: function (begin, end)
{
return new Uint8Array(Array.prototype.slice.call(this, begin, end));
}
});
}
if (!Array.prototype.includes) {
Array.prototype.includes = x => Array.prototype.indexOf(x) >= 0;
}
}
class AesGcmEncryptResult {
ciphertext: ArrayBuffer;
tag: ArrayBuffer;
}
// from https://github.com/vibornoff/webcrypto-shim/blob/master/webcrypto-shim.js
function webcryptoShim(global: any) {
if ( typeof Promise !== 'function' )
throw "Promise support required";
var _crypto = global.crypto || global.msCrypto;
if ( !_crypto ) return;
var _subtle = _crypto.subtle || _crypto.webkitSubtle;
if ( !_subtle ) return;
var _Crypto = global.Crypto || _crypto.constructor || Object,
_SubtleCrypto = global.SubtleCrypto || _subtle.constructor || Object,
_CryptoKey = global.CryptoKey || global.Key || Object;
var isEdge = global.navigator.userAgent.indexOf('Edge/') > -1;
var isIE = !!global.msCrypto && !isEdge;
var isWebkit = !_crypto.subtle && !!_crypto.webkitSubtle;
if ( !isIE && !isWebkit ) return;
function s2a ( s ) {
return btoa(s).replace(/\=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
}
function a2s ( s ) {
s += '===', s = s.slice( 0, -s.length % 4 );
return atob( s.replace(/-/g, '+').replace(/_/g, '/') );
}
function s2b ( s ) {
var b = new Uint8Array(s.length);
for ( var i = 0; i < s.length; i++ ) b[i] = s.charCodeAt(i);
return b;
}
function b2s ( b ) {
if ( b instanceof ArrayBuffer ) b = new Uint8Array(b);
return String.fromCharCode.apply( String, b );
}
function alg ( a ) {
var r = { 'name': (a.name || a || '').toUpperCase().replace('V','v') };
switch ( r.name ) {
case 'SHA-1':
case 'SHA-256':
case 'SHA-384':
case 'SHA-512':
break;
case 'AES-CBC':
case 'AES-GCM':
case 'AES-KW':
if ( a.length ) r['length'] = a.length;
break;
case 'HMAC':
if ( a.hash ) r['hash'] = alg(a.hash);
if ( a.length ) r['length'] = a.length;
break;
case 'RSAES-PKCS1-v1_5':
if ( a.publicExponent ) r['publicExponent'] = new Uint8Array(a.publicExponent);
if ( a.modulusLength ) r['modulusLength'] = a.modulusLength;
break;
case 'RSASSA-PKCS1-v1_5':
case 'RSA-OAEP':
if ( a.hash ) r['hash'] = alg(a.hash);
if ( a.publicExponent ) r['publicExponent'] = new Uint8Array(a.publicExponent);
if ( a.modulusLength ) r['modulusLength'] = a.modulusLength;
break;
default:
throw new SyntaxError("Bad algorithm name");
}
return r;
};
function jwkAlg ( a ) {
return {
'HMAC': {
'SHA-1': 'HS1',
'SHA-256': 'HS256',
'SHA-384': 'HS384',
'SHA-512': 'HS512',
},
'RSASSA-PKCS1-v1_5': {
'SHA-1': 'RS1',
'SHA-256': 'RS256',
'SHA-384': 'RS384',
'SHA-512': 'RS512',
},
'RSAES-PKCS1-v1_5': {
'': 'RSA1_5',
},
'RSA-OAEP': {
'SHA-1': 'RSA-OAEP',
'SHA-256': 'RSA-OAEP-256',
},
'AES-KW': {
'128': 'A128KW',
'192': 'A192KW',
'256': 'A256KW',
},
'AES-GCM': {
'128': 'A128GCM',
'192': 'A192GCM',
'256': 'A256GCM',
},
'AES-CBC': {
'128': 'A128CBC',
'192': 'A192CBC',
'256': 'A256CBC',
},
}[a.name][ ( a.hash || {} ).name || a.length || '' ];
}
function b2jwk ( k ) {
if ( k instanceof ArrayBuffer || k instanceof Uint8Array ) k = JSON.parse( decodeURIComponent( escape( b2s(k) ) ) );
var jwk:any = { 'kty': k.kty, 'alg': k.alg, 'ext': k.ext || k.extractable };
switch ( jwk.kty ) {
case 'oct':
jwk.k = k.k;
case 'RSA':
[ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi', 'oth' ].forEach( function ( x ) { if ( x in k ) jwk[x] = k[x] } );
break;
default:
throw new TypeError("Unsupported key type");
}
return jwk;
}
function jwk2b ( k ) {
var jwk = b2jwk(k);
if ( isIE ) jwk['extractable'] = jwk.ext, delete jwk.ext;
return s2b( unescape( encodeURIComponent( JSON.stringify(jwk) ) ) ).buffer;
}
function pkcs2jwk ( k ) {
var info = b2der(k), prv = false;
if ( info.length > 2 ) prv = true, info.shift(); // remove version from PKCS#8 PrivateKeyInfo structure
var jwk = { 'ext': true };
switch ( info[0][0] ) {
case '1.2.840.113549.1.1.1':
var rsaComp = [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ],
rsaKey = b2der( info[1] );
if ( prv ) rsaKey.shift(); // remove version from PKCS#1 RSAPrivateKey structure
for ( var i = 0; i < rsaKey.length; i++ ) {
if ( !rsaKey[i][0] ) rsaKey[i] = rsaKey[i].subarray(1);
jwk[ rsaComp[i] ] = s2a( b2s( rsaKey[i] ) );
}
jwk['kty'] = 'RSA';
break;
default:
throw new TypeError("Unsupported key type");
}
return jwk;
}
function jwk2pkcs ( k ) {
var key, info:any = [ [ '', null ] ], prv = false;
switch ( k.kty ) {
case 'RSA':
var rsaComp = [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ],
rsaKey = [];
for ( var i = 0; i < rsaComp.length; i++ ) {
if ( !( rsaComp[i] in k ) ) break;
var b = rsaKey[i] = s2b( a2s( k[ rsaComp[i] ] ) );
if ( b[0] & 0x80 ) rsaKey[i] = new Uint8Array(b.length + 1), rsaKey[i].set( b, 1 );
}
if ( rsaKey.length > 2 ) prv = true, rsaKey.unshift( new Uint8Array([0]) ); // add version to PKCS#1 RSAPrivateKey structure
info[0][0] = '1.2.840.113549.1.1.1';
key = rsaKey;
break;
default:
throw new TypeError("Unsupported key type");
}
info.push( new Uint8Array( der2b(key) ).buffer );
if ( !prv ) info[1] = { 'tag': 0x03, 'value': info[1] };
else info.unshift( new Uint8Array([0]) ); // add version to PKCS#8 PrivateKeyInfo structure
return new Uint8Array( der2b(info) ).buffer;
}
var oid2str = { 'KoZIhvcNAQEB': '1.2.840.113549.1.1.1' },
str2oid = { '1.2.840.113549.1.1.1': 'KoZIhvcNAQEB' };
function b2der ( buf, ctx? ) {
if ( buf instanceof ArrayBuffer ) buf = new Uint8Array(buf);
if ( !ctx ) ctx = { pos: 0, end: buf.length };
if ( ctx.end - ctx.pos < 2 || ctx.end > buf.length ) throw new RangeError("Malformed DER");
var tag = buf[ctx.pos++],
len = buf[ctx.pos++];
if ( len >= 0x80 ) {
len &= 0x7f;
if ( ctx.end - ctx.pos < len ) throw new RangeError("Malformed DER");
for ( var xlen = 0; len--; ) xlen <<= 8, xlen |= buf[ctx.pos++];
len = xlen;
}
if ( ctx.end - ctx.pos < len ) throw new RangeError("Malformed DER");
var rv;
switch ( tag ) {
case 0x02: // Universal Primitive INTEGER
rv = buf.subarray( ctx.pos, ctx.pos += len );
break;
case 0x03: // Universal Primitive BIT STRING
if ( buf[ctx.pos++] ) throw new Error( "Unsupported bit string" );
len--;
case 0x04: // Universal Primitive OCTET STRING
rv = new Uint8Array( buf.subarray( ctx.pos, ctx.pos += len ) ).buffer;
break;
case 0x05: // Universal Primitive NULL
rv = null;
break;
case 0x06: // Universal Primitive OBJECT IDENTIFIER
var oid = btoa( b2s( buf.subarray( ctx.pos, ctx.pos += len ) ) );
if ( !( oid in oid2str ) ) throw new Error( "Unsupported OBJECT ID " + oid );
rv = oid2str[oid];
break;
case 0x30: // Universal Constructed SEQUENCE
rv = [];
for ( var end = ctx.pos + len; ctx.pos < end; ) rv.push( b2der( buf, ctx ) );
break;
default:
throw new Error( "Unsupported DER tag 0x" + tag.toString(16) );
}
return rv;
}
function der2b ( val, buf? ) {
if ( !buf ) buf = [];
var tag = 0, len = 0,
pos = buf.length + 2;
buf.push( 0, 0 ); // placeholder
if ( val instanceof Uint8Array ) { // Universal Primitive INTEGER
tag = 0x02, len = val.length;
for ( var i = 0; i < len; i++ ) buf.push( val[i] );
}
else if ( val instanceof ArrayBuffer ) { // Universal Primitive OCTET STRING
tag = 0x04, len = val.byteLength, val = new Uint8Array(val);
for ( var i = 0; i < len; i++ ) buf.push( val[i] );
}
else if ( val === null ) { // Universal Primitive NULL
tag = 0x05, len = 0;
}
else if ( typeof val === 'string' && val in str2oid ) { // Universal Primitive OBJECT IDENTIFIER
var oid = s2b( atob( str2oid[val] ) );
tag = 0x06, len = oid.length;
for ( var i = 0; i < len; i++ ) buf.push( oid[i] );
}
else if ( val instanceof Array ) { // Universal Constructed SEQUENCE
for ( var i = 0; i < val.length; i++ ) der2b( val[i], buf );
tag = 0x30, len = buf.length - pos;
}
else if ( typeof val === 'object' && val.tag === 0x03 && val.value instanceof ArrayBuffer ) { // Tag hint
val = new Uint8Array(val.value), tag = 0x03, len = val.byteLength;
buf.push(0); for ( var i = 0; i < len; i++ ) buf.push( val[i] );
len++;
}
else {
throw new Error( "Unsupported DER value " + val );
}
if ( len >= 0x80 ) {
var xlen = len, len = 4;
buf.splice( pos, 0, (xlen >> 24) & 0xff, (xlen >> 16) & 0xff, (xlen >> 8) & 0xff, xlen & 0xff );
while ( len > 1 && !(xlen >> 24) ) xlen <<= 8, len--;
if ( len < 4 ) buf.splice( pos, 4 - len );
len |= 0x80;
}
buf.splice( pos - 2, 2, tag, len );
return buf;
}
function CryptoKey ( key, alg, ext, use ) {
Object.defineProperties( this, {
_key: {
value: key
},
type: {
value: key.type,
enumerable: true,
},
extractable: {
value: (ext === undefined) ? key.extractable : ext,
enumerable: true,
},
algorithm: {
value: (alg === undefined) ? key.algorithm : alg,
enumerable: true,
},
usages: {
value: (use === undefined) ? key.usages : use,
enumerable: true,
},
});
}
function isPubKeyUse ( u ) {
return u === 'verify' || u === 'encrypt' || u === 'wrapKey';
}
function isPrvKeyUse ( u ) {
return u === 'sign' || u === 'decrypt' || u === 'unwrapKey';
}
[ 'generateKey', 'importKey', 'unwrapKey' ]
.forEach( function ( m ) {
var _fn = _subtle[m];
_subtle[m] = function ( a, b, c ) {
var args = [].slice.call(arguments),
ka, kx, ku;
switch ( m ) {
case 'generateKey':
ka = alg(a), kx = b, ku = c;
break;
case 'importKey':
ka = alg(c), kx = args[3], ku = args[4];
if ( a === 'jwk' ) {
b = b2jwk(b);
if ( !b.alg ) b.alg = jwkAlg(ka);
if ( !b.key_ops ) b.key_ops = ( b.kty !== 'oct' ) ? ( 'd' in b ) ? ku.filter(isPrvKeyUse) : ku.filter(isPubKeyUse) : ku.slice();
args[1] = jwk2b(b);
}
break;
case 'unwrapKey':
ka = args[4], kx = args[5], ku = args[6];
args[2] = c._key;
break;
}
if ( m === 'generateKey' && ka.name === 'HMAC' && ka.hash ) {
ka.length = ka.length || { 'SHA-1': 512, 'SHA-256': 512, 'SHA-384': 1024, 'SHA-512': 1024 }[ka.hash.name];
return _subtle.importKey( 'raw', _crypto.getRandomValues( new Uint8Array( (ka.length+7)>>3 ) ), ka, kx, ku );
}
if ( isWebkit && m === 'generateKey' && ka.name === 'RSASSA-PKCS1-v1_5' && ( !ka.modulusLength || ka.modulusLength >= 2048 ) ) {
a = alg(a), a.name = 'RSAES-PKCS1-v1_5', delete a.hash;
return _subtle.generateKey( a, true, [ 'encrypt', 'decrypt' ] )
.then( function ( k ) {
return Promise.all([
_subtle.exportKey( 'jwk', k.publicKey ),
_subtle.exportKey( 'jwk', k.privateKey ),
]);
})
.then( function ( keys ) {
keys[0].alg = keys[1].alg = jwkAlg(ka);
keys[0].key_ops = ku.filter(isPubKeyUse), keys[1].key_ops = ku.filter(isPrvKeyUse);
return Promise.all([
_subtle.importKey( 'jwk', keys[0], ka, true, keys[0].key_ops ),
_subtle.importKey( 'jwk', keys[1], ka, kx, keys[1].key_ops ),
]);
})
.then( function ( keys ) {
return {
publicKey: keys[0],
privateKey: keys[1],
};
});
}
if ( ( isWebkit || ( isIE && ( ka.hash || {} ).name === 'SHA-1' ) )
&& m === 'importKey' && a === 'jwk' && ka.name === 'HMAC' && b.kty === 'oct' ) {
return _subtle.importKey( 'raw', s2b( a2s(b.k) ), c, args[3], args[4] );
}
if ( isWebkit && m === 'importKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
return _subtle.importKey( 'jwk', pkcs2jwk(b), c, args[3], args[4] );
}
if ( isIE && m === 'unwrapKey' ) {
return _subtle.decrypt( args[3], c, b )
.then( function ( k ) {
return _subtle.importKey( a, k, args[4], args[5], args[6] );
});
}
var op;
try {
op = _fn.apply( _subtle, args );
}
catch ( e ) {
return Promise.reject(e);
}
if ( isIE ) {
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) { rej(e) };
op.oncomplete = function ( r ) { res(r.target.result) };
});
}
op = op.then( function ( k ) {
if ( ka.name === 'HMAC' ) {
if ( !ka.length ) ka.length = 8 * k.algorithm.length;
}
if ( ka.name.search('RSA') == 0 ) {
if ( !ka.modulusLength ) ka.modulusLength = (k.publicKey || k).algorithm.modulusLength;
if ( !ka.publicExponent ) ka.publicExponent = (k.publicKey || k).algorithm.publicExponent;
}
if ( k.publicKey && k.privateKey ) {
k = {
publicKey: new CryptoKey( k.publicKey, ka, kx, ku.filter(isPubKeyUse) ),
privateKey: new CryptoKey( k.privateKey, ka, kx, ku.filter(isPrvKeyUse) ),
};
}
else {
k = new CryptoKey( k, ka, kx, ku );
}
return k;
});
return op;
}
});
[ 'exportKey', 'wrapKey' ]
.forEach( function ( m ) {
var _fn = _subtle[m];
_subtle[m] = function ( a, b, c ) {
var args = [].slice.call(arguments);
switch ( m ) {
case 'exportKey':
args[1] = b._key;
break;
case 'wrapKey':
args[1] = b._key, args[2] = c._key;
break;
}
if ( ( isWebkit || ( isIE && ( b.algorithm.hash || {} ).name === 'SHA-1' ) )
&& m === 'exportKey' && a === 'jwk' && b.algorithm.name === 'HMAC' ) {
args[0] = 'raw';
}
if ( isWebkit && m === 'exportKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
args[0] = 'jwk';
}
if ( isIE && m === 'wrapKey' ) {
return _subtle.exportKey( a, b )
.then( function ( k ) {
if ( a === 'jwk' ) k = s2b( unescape( encodeURIComponent( JSON.stringify( b2jwk(k) ) ) ) );
return _subtle.encrypt( args[3], c, k );
});
}
var op;
try {
op = _fn.apply( _subtle, args );
}
catch ( e ) {
return Promise.reject(e);
}
if ( isIE ) {
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) { rej(e) };
op.oncomplete = function ( r ) { res(r.target.result) };
});
}
if ( m === 'exportKey' && a === 'jwk' ) {
op = op.then( function ( k ) {
if ( ( isWebkit || ( isIE && ( b.algorithm.hash || {} ).name === 'SHA-1' ) )
&& b.algorithm.name === 'HMAC') {
return { 'kty': 'oct', 'alg': jwkAlg(b.algorithm), 'key_ops': b.usages.slice(), 'ext': true, 'k': s2a( b2s(k) ) };
}
k = b2jwk(k);
if ( !k.alg ) k['alg'] = jwkAlg(b.algorithm);
if ( !k.key_ops ) k['key_ops'] = ( b.type === 'public' ) ? b.usages.filter(isPubKeyUse) : ( b.type === 'private' ) ? b.usages.filter(isPrvKeyUse) : b.usages.slice();
return k;
});
}
if ( isWebkit && m === 'exportKey' && ( a === 'spki' || a === 'pkcs8' ) ) {
op = op.then( function ( k ) {
k = jwk2pkcs( b2jwk(k) );
return k;
});
}
return op;
}
});
[ 'encrypt', 'decrypt', 'sign', 'verify' ]
.forEach( function ( m ) {
var _fn = _subtle[m];
_subtle[m] = function ( a, b, c, d ) {
if ( isIE && ( !c.byteLength || ( d && !d.byteLength ) ) )
throw new Error("Empy input is not allowed");
var args = [].slice.call(arguments),
ka = alg(a);
if ( isIE && ( m === 'encrypt' || m === 'decrypt' ) && b.algorithm.hash ) {
args[0].hash = args[0].hash || b.algorithm.hash;
}
if ( isIE && m === 'decrypt' && ka.name === 'AES-GCM' ) {
var tl = a.tagLength >> 3;
args[2] = (c.buffer || c).slice( 0, c.byteLength - tl ),
a.tag = (c.buffer || c).slice( c.byteLength - tl );
}
args[1] = b._key;
var op;
try {
op = _fn.apply( _subtle, args );
}
catch ( e ) {
return Promise.reject(e);
}
if ( isIE ) {
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) {
rej(e);
};
op.oncomplete = function ( r ) {
var r = r.target.result;
if ( m === 'encrypt' && r instanceof AesGcmEncryptResult ) {
var c = r.ciphertext, t = r.tag;
r = new Uint8Array( c.byteLength + t.byteLength );
r.set( new Uint8Array(c), 0 );
r.set( new Uint8Array(t), c.byteLength );
r = r.buffer;
}
res(r);
};
});
}
return op;
}
});
if ( isIE ) {
var _digest = _subtle.digest;
_subtle['digest'] = function ( a, b ) {
if ( !b.byteLength )
throw new Error("Empy input is not allowed");
var op;
try {
op = _digest.call( _subtle, a, b );
}
catch ( e ) {
return Promise.reject(e);
}
op = new Promise( function ( res, rej ) {
op.onabort =
op.onerror = function ( e ) { rej(e) };
op.oncomplete = function ( r ) { res(r.target.result) };
});
return op;
};
global.crypto = Object.create( _crypto, {
getRandomValues: { value: function ( a ) { return new Array(_crypto.getRandomValues(a)) } },
subtle: { value: _subtle },
});
global.CryptoKey = CryptoKey;
}
if ( isWebkit ) {
_crypto.subtle = _subtle;
global.Crypto = _Crypto;
global.SubtleCrypto = _SubtleCrypto;
global.CryptoKey = CryptoKey;
}
}; |
Hi folks. Sorry to keep you all waiting. I'm doing some cleanup in the polyfills to make sure we only load what we need (we'll also get rid of the |
Build please!!! |
Hey 👋
I have a Vue CLI app that I've adding this package to via npm. I have it all working in modern browsers, but IE 11 throws a number of errors related to missing features (i.e.,
String.prototype.startsWith
).I could be totally off here, but it looks like
dist/auth0-spa-js.production.js
is built to be ES5 compatible, and work in IE 11?If not, my follow-up question: What are the polyfills this package needs for IE 11?
Vue CLI recommends either explicitly including the polyfills needed (which I've done with other dependencies), or just including all polyfills for your target browsers. I'd rather not include all polyfills if possible.
The text was updated successfully, but these errors were encountered: