问题
I have tried a number of angular-adal libraries but the renew of the token is not automatic done.
This the configuration I used.
In package.json
"@types/adal": "^1.0.29",
"@types/adal-angular": "^1.0.0",
"adal-angular": "^1.0.17",
adal-angular come with two scripts adal.js
and adal-angular.js
. I think adal.angular.js
is only for old angularjs
solutions. So I used adal.js and an wrapper @types/adal.
and include the adal.js
in the .angular-cli.json
"scripts": [
"../node_modules/adal-angular/lib/adal.js"
],
In my angular 5 application I use adal to log on and make request api request to website on another url.
The used config
JwtConfig: {
tenant: "a1d50521-9687-4e4d-a76d-xxxxxxxxx",
clientId: "8d708afe-2966-40b7-918c-xxxxxxxx",
isAngular: true
},
My authService looks like
import { } from "adal";
@Injectable()
export class AuthService {
private _config: adal.Config;
private _context: adal.AuthenticationContext;
constructor() {
Logging = {
level: 3,
log: function (message) {
console.log(message);
}
};
this._config = environment.JwtConfig;
this._context = new AuthenticationContext(this._config);
}
The logging is not needed but enables adal.js logging
A lot of examples store there token in localstorage but this token is only valid for 1 hour. To solve this problem I call acquireToken everytime. It will give me the cached token or a renewed if it is expired.
acquireToken(): Observable<string> {
return new Observable<string>((subscriber: Subscriber<string>) => {
if (window.frameElement && window.frameElement.id === "adalIdTokenFrame")
subscriber.next(null);
else {
const user = this._context.getCachedUser();
return this._context.acquireToken(environment.JwtConfig.clientId, (message: string, token: string) => {
subscriber.next(token);
});
}
});
}
To get this working right there are a number of tricky things.
The renewal is done in a hidden I frame what makes a request to microsoft AD
https://login.microsoftonline.com/xxxtenantIDxxx/oauth2/authorize?response_type=id_token&client_id=xxx-xx-x-xx
the response will redirect to http://localhost:4200/...
that will start another angular application in this hidden IFrame
this check if (window.frameElement && window.frameElement.id === "adalIdTokenFrame")
will prevent an endless loop of hidden IFrames.
The line const user = this._context.getCachedUser();
is needed so that adal knows there is a user and will renew the user instead a message that the user must login.
This seems to work ok. If the token is expired New Date(profile.exp*1000)
for several hours. The user can still renew this token.
Is there a way to prevent that my Angular apllication is loaded in the hidden Iframe? With a working wrapper or some other trick?
回答1:
Add a script to prevent loading angular in the hidden frame. This makes the logon/renew of token faster. It prevent that angular is started tree times when loaded for the first time in the browser.
This script can be added to the index.html. It check if it is loaded in a hidden frame, decode the token and prevent loading angular.
<script>
if (window.parent && window.parent.AuthenticationContext) {
var self = window.parent._adalInstance;
var hash = window.location.hash;
if (self.isCallback(hash)) {
self.info("Returned from redirect url");
var requestInfo = self.getRequestInfo(hash);
var tokenReceivedCallback = self._callBackMappedToRenewStates[requestInfo.stateResponse];
self.saveTokenFromHash(requestInfo);
var token = requestInfo.parameters[self.CONSTANTS.ACCESS_TOKEN] || requestInfo.parameters[self.CONSTANTS.ID_TOKEN];
var tokenType = self.CONSTANTS.ACCESS_TOKEN;
var errorDesc = requestInfo.parameters[self.CONSTANTS.ERROR_DESCRIPTION];
var error = requestInfo.parameters[self.CONSTANTS.ERROR];
try {
if (tokenReceivedCallback)
tokenReceivedCallback(errorDesc, token, error, tokenType);
} catch (err) {
self.error("Error occurred in user defined callback function: " + err);
}
document.write('<style type="text/undefined">');
}
}
</script>
Update: with Adal en MSAL you can use popup but some IE versions have problems with this. MSAL is not as complex as adal
import * as Msal from 'msal';
export const config: Msal.Configuration {
auth: { clientId: '.....-1796-...'},
cache: { cacheLocation: 'localStorage' },
};
constructor() {
this.adAuthenticationContext = new Msal.UserAgentApplication(config);
}
回答2:
See the Adal.js Wiki for more info. From that page:
Adal uses iframes to renew tokens silently in the background. AAD returns the token back to the redirect_uri specified in the token request (this redirect_uri must be registered with AAD). Since the response is a 302, it results in the html corresponding to the redirect_uri getting loaded in the iframe. Usually, it's the app's root/default page. This causes an app's reload. In other cases, if the app's root page requires authentication, it might lead to nested iframes or xframe deny error. Since, adal cannot cancel the 302 issued by AAD, it cannot prevent the redirect_uri from getting loaded in the iframe. But, there are workarounds for this that one can use to avoid the entire app reloading again or other errors caused because of this:
Specify a different html for the iframe:
Set redirect_uri property on config to a simple page, that does not require authentication. You have to make sure that it matches with the redirect_uri registered in AAD portal. This will not affect user's login experience as Adal saves the start page when user begins the login process and redirects back to the exact location after login is completed.
Please look at the gist for an example of template html you can use. You will need to make some modifications to the html for it to work with your specific app: https://gist.github.com/tushargupta51/5fa6d000357120adbc8fd1d5c68853c4
Conditional initialization in your main app.js file: If your app is structured similar to our sample single-page app where there is one central Javascript file (app.js in the sample) that defines the app's initialization, routing and other stuff, you can use an if...else based on whether the app is loading in an iframe or not. Something like this: https://gist.github.com/tushargupta51/78ce0b6bce998f6851abd02d91eb3a95
Basically they are saying either
1) Redirect to a page that can parse the token but not load the entire app, or
2) Redirect to the app root and conditionally load the main javascript bundles. Detect if its an iframe, and if so, don't load the app bundle.
I think @Thom Kiesewetter solution above is right in line with their suggested workarounds. It basically stops loading any javascript after the script he wrote, if its within an iframe.
来源:https://stackoverflow.com/questions/49099740/angular-5-adal-js-automatic-token-renew-in-load-angular-twice-or-more