I have found one approach that seems to work for certain use cases. If the protected service happens to also have some way to send a valid image -- maybe they also protected a "login complete" page that has images on it, maybe they protected the Swagger docs page, doesn't matter -- then you can do an img
tag test like this:
// Use a hidden `<img>` tag to test if the provided (protected) resource URL
// can be loaded. Resolves `true` if the image loads, or `false` if the image
// fails to load. Rejects if the provided timeout elapses before resolution.
function testAuthWithImage(imgUrl: string, timeoutMS: number): Promise<boolean> {
return new Promise((resolve, reject) => {
const canary = document.createElement("img");
function cleanup() {
window.clearTimeout(timeout);
// Must remove event listeners so GC can clean up canary
canary.removeEventListener("load", loaded);
canary.removeEventListener("error", failed);
}
async function loaded() {
cleanup();
resolve(true);
}
async function failed() {
cleanup();
resolve(false);
}
const timeout = window.setTimeout(() => {
cleanup();
reject("Connection timed out");
}, timeoutMS);
canary.addEventListener("load", loaded);
canary.addEventListener("error", failed);
// Assigning ths will cause the image to load or fail
canary.src = imgUrl;
});
}
As far as I can tell, it looks like all modern browsers will discard the 401
response without a login prompt when it's for a "subresource" in a different domain, as a means of phishing protection. Once I figured this out, handling customized login flow is easy:
protected async checkLogin(promptForPass: boolean = false): Promise<UserIdentity | undefined> {
if (await testAuthWithImage(this.TEST_IMG_ENDPOINT.url, this.timeoutMS)) {
// The image-test worked so we have an existing session; just check the profile.
try { return await this.fetchUserInfo(); }
catch (err) {
// If it was an HttpErrorResponse, throw the `message`
throw err.message || err;
}
} else if (promptForPass) {
// If the test failed but we're prompting, show user/pass dialog immediately
return await this.doLogin();
}
// If we got here, we're not prompting so return undefined
}
I think it should degrade gracefully in legacy browsers, because the subresource load (img
tag) would cause a native login prompt, then fail or succeed based on what the user does with it. It won't work if the server doesn't already provide some suitable protected resource, though, and it does require at least one spurious request for the image in question, so I'd welcome a better answer.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…