BrowserAuthError: interaction_in_progress - Unable to fix, regardles of solutions found - javascript

I'm implementing security for the applications at the company I'm working at right now. I'm using #azure/msal-angular#2.0.2, #azure/msal-browser#2.16.1. I followed the example found here
and got it working for the first application. I went on to implement it for the next application, which is basically the same one, just talks to a different api, but the complexity is the same. After possibly doing something wrong I keep getting the error:
core.js:6157 ERROR Error: Uncaught (in promise): BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
I found several other posts that said to clear the caches, storages etc... But none of it works. All it does is prompt me to log in again, only to fill in the sessionStorage back with the same entries, amongst them the entry that says msal.442edcf7-9e0c-469b-93fb-daa1839724bd.interaction.status interaction_in_progress. As far as I know I've tried everthing I've found. Also the solution from AzureAD themselves doesn't work.
I'm very new to this so I might have missed something simple, if so, my apologies.
My code can be found below.
Angular version
11.0.2
app.module.ts
import {
MsalBroadcastService,
MsalGuard,
MsalInterceptor,
MsalModule, MsalRedirectComponent,
MsalService
} from "#azure/msal-angular";
import {BrowserCacheLocation, InteractionType, LogLevel, PublicClientApplication} from '#azure/msal-browser';
const isIE = window.navigator.userAgent.indexOf('MSIE ') > -1 || window.navigator.userAgent.indexOf('Trident/') > -1;
#NgModule({
declarations: [
AppComponent,
HeaderComponent,
RibonComponent
],
imports: [
BrowserModule,
AppRoutingModule,
...,
MsalModule.forRoot(
new PublicClientApplication({ // MSAL Configuration
auth: {
clientId: "442edcf7-...",
authority: "https://login.microsoftonline.com/81fa766e-...",
redirectUri: window.location.origin,
},
cache: {
cacheLocation : BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: false, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback: (level: LogLevel, message: string, containsPii: boolean): void => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
}
},
piiLoggingEnabled: false
}
}
}), {
interactionType: InteractionType.Popup, // MSAL Guard Configuration
}, {
protectedResourceMap: new Map([
[ 'http://localhost:8400/', ['api://442edcf7-9e0c-469b-93fb-daa1839724bd/acces_as_user/Acces-user']],
[ 'https://quality-score-dev-pcq-dev.bravo-apps.volvocars.biz/', ['api://442edcf7-9e0c-469b-93fb-daa1839724bd/acces_as_user/Acces-user']]
]),
interactionType: InteractionType.Redirect // MSAL Interceptor Configuration
}
)
],
providers: [
EnvServiceProvider,
{
provide: MatPaginatorIntl,
useClass: MatPaginatorIntlTranslator
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
MsalService,
MsalGuard,
MsalBroadcastService,
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy{
title = 'Quality score';
isIframe = false;
loginDisplay = false;
activeUser = '';
private readonly onDestroy$ = new Subject<void>()
constructor(
#Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration,
private languageService: LanguageService,
private authService: MsalService,
private msalBroadcastService: MsalBroadcastService,
private location: Location,
private userService: UserService
){}
ngOnInit(): void {
const currentPath = this.location.path();
// Dont perform nav if in iframe or popup, other than for front-channel logout
this.isIframe = BrowserUtils.isInIframe() && !window.opener && currentPath.indexOf("logout") < 0; // Remove this line to use Angular Universal
this.msalBroadcastService.inProgress$
.pipe(
filter((status: InteractionStatus) => status === InteractionStatus.None),
takeUntil(this.onDestroy$)
)
.subscribe(() => {
this.setLoginDisplay();
this.checkAndSetActiveAccount();
})
}
ngOnDestroy() {
this.onDestroy$.next();
}
setLoginDisplay() {
this.loginDisplay = this.authService.instance.getAllAccounts().length > 0;
}
checkAndSetActiveAccount(){
/**
* If no active account set but there are accounts signed in, sets first account to active account
* To use active account set here, subscribe to inProgress$ first in your component
* Note: Basic usage demonstrated. Your app may require more complicated account selection logic
*/
let activeAccount = this.authService.instance.getActiveAccount();
if (!activeAccount && this.authService.instance.getAllAccounts().length > 0) {
let accounts = this.authService.instance.getAllAccounts();
this.authService.instance.setActiveAccount(accounts[0]);
}
if(activeAccount) {
this.userService.activeUserName = activeAccount.name;
this.activeUser = activeAccount.name;
}
}
logout(): void{
this.authService.logoutRedirect();
}
}
I'm really lost and have no idea what I have done wrong. From my understanding there is a login process that was interupted, probably by me leaving the login screen, but now I have no idea how to "finish it up" or complete the process.
Update
I tried copying the LocalStorage values from the working application and I got it to work. Refreshing works and no errors appear, but when I logout and after it prompted me to login again and I do, then it's right back to the start again.
Solution update
I've had a breakthrough. If I change the login type to Popup and handle it this way, it's fixed. I can login and logout without any issues. However if I then change it back to Redirect, it's broken again. So for now I'll keep it on Popup. Simple solution, but I hadn't thought of it because I assumed the issue would be occur there as well.

I can't tell by the code provided, but I had a similar issue. On top of that I was getting the following error in the console: Error: The selector "app-redirect" did not match any elements
This lead me to the following post: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3114#issuecomment-788394239
I added <app-redirect></app-redirect> to the index.html page, below app-root as suggested and this seemed to sort both issues.

"Workaround" fix
Make your login type Popup. Dumb of me not to think about that

Related

Cookie not being saved (Angular, Express)

Cookie received in chrome dev tools > network > cookies but not appearing in application > cookies
I have a login function that sets a cookie when a user logs in. It is appearing in the cookies column under the network tab but not in the application tab.
this is the function in express handling the cookie setting. This function is called when there is a post request made to the /login/ route.
const loginUser = async (req, res) => {
const dbRes = await authenticateUser(req.body);
if (dbRes.authenticated === true) {
res.setHeader(
"set-cookie",
`session=${dbRes.cookie};
path=/; samesite=lax`
);
// I've also tried using res.cookie()
res.status(200).json({ status: "authenticated" });
} else if (dbRes.authenticated === false) {
res.status(401).json({ status: "wrong username, email or password" });
} else {
res.status(dbRes.status).json({ error: dbRes.error });
}
};
Cookie setting works as expected when i do it like this and i go to the /cookie/ route in browser.
app.use("/cookie/", (req, res) => {
res.setHeader("set-cookie", "foo=bar");
res.send("set");
});
I have an angular frontend sending a post request to /login/ route but the login route is undefined in my angular app so the user will be redirected to / route.
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { UserLoggedInGuard } from './guards/user-logged-in.guard';
const routes: Routes = [
{
path: '',
loadChildren: () =>
import('./features/login/login.module').then((m) => m.LoginModule),
},
{
path: 'feed',
loadChildren: () => import('./feed/feed.module').then((m) => m.FeedModule),
canActivate: [UserLoggedInGuard],
},
{
path: '**',
redirectTo: '',
},
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
However, the /cookie/ route set by express is not redirected and the cookie is being set correctly. I'm wondering if there are any conflicts between the angular and express router making setting cookies problematic.
I need help understanding why the cookie is not appearing in the application tab although it was received by the browser under the network tab.
--
As per Kavinda's answer, I tried adding useCredentials to my frontend code like so
loginUser(data: LoginForm) {
return this.httpClient.post(`${this.domainLink}/login`, data, {
withCredentials: true,
});
}
Unfortunately, this fix isnt working for me either even after setting credentials to true in the backend.
--
I've resorted to using ngx-cookie in Angular to set the cookie for now instead. However, I'd still appreciate it if someone can explain what I'm doing wrong here in the backend. Thanks!
I'll include my current workaround below in hopes it will help someone too
login.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { LoginRoutingModule } from './login-routing.module';
import { LoginComponent } from './login.component';
import { CookieModule } from 'ngx-cookie';
import { CookieService } from 'ngx-cookie';
#NgModule({
declarations: [LoginComponent],
imports: [
CommonModule,
LoginRoutingModule,
FormsModule,
ReactiveFormsModule,
CookieModule.withOptions(),
],
providers: [CookieService],
})
export class LoginModule {}
login.component.ts (res is the response from server where i returned a json value with the cookie value stored in the cookie key.
this.cookieService.put('session', res.cookie);
After testing it out it appears that chrome (86.0.4240.198) still allows cross site cookies on localhost which means that your problem is not caused by the new restrictions and using { useCredentials: true } should work fine.
Found my problem. I was running on the domain 'localhost' but my request link was going to '127.0.0.1' instead. Cookies cannot be set from a different domain.
Not setting the withCredentials flag to true originally didn't help my case either.

Angularjs and Angular 8 hybrid and execute code micronization get Error: Can't resolve all parameters for xxx-service

I have a difficult case which was organized from an old angularjs and angular8, besides it's was also limited in the extensive refactoring to resolve in code of micronization.And the code of angularjs cannot be changed.
In the end I choose the library ngx-planet that is closest to my situation, but i got this error
Issue:
When I then local run it, I get this error message.
I have tested this way of writing under angularjs and angular8 code without planet library, it can work, but after using planet, the aforementioned error occurred.
After searching, it was found that the reasons for this error message are known as the following 6 types:
Barrel index
Circularity dependency
Forgot to enable polyfills import'core-js/es6/reflect'
Injectable decorator incorrect usage (EX: missing # or capital & lower case error etc...)
Tsconfig does not configure emitDecoratorMetadata
Decorate parameter type use any in your constructor
The first 5 have been excluded, I suspect it is the last, because of this Configuring Dependency Injection in Angular
But I am confused, whether a certain configuration of planet causes parameter type to fail?
Code Structure:
1. There is a common service exported from angularjs
(File name: angular1-root-module.js)
(function () {
angular.module('angular1', [
'angular1.export-service'
]);
angular.module('angular1.export-service', []);
angular.module('angular1.export-service').factory('Angular1ExportService', Angular1ExportService);
Angular1ExportService.$inject = [];
function Angular1ExportService() {
function outPutString() {
return 'I from Angular1 export service string';
}
return {
outPutAngular1String: outPutString,
};
}
})();
2. Inject into the class named Angular1InjectorService through the factory provider and depend on angularjs's $injector
export function Angular1InjectorServiceFactory(injector: any) {
return new Angular1InjectorService(injector);
}
export const Angular1InjectorServiceProvider = {
provide: Angular1InjectorService,
useFactory: Angular1InjectorServiceFactory,
deps: ['$injector']
};
#Injectable()
export class Angular1InjectorService {
// I think probably this injector of type is any cause
constructor(private angular1Injector: any) {
}
getService(serviceName: String) {
return this.angular1Injector.get(serviceName);
}
}
3. Then inject into the common AppBaseService
#Injectable()
export class AppBaseService {
readonly angular1InjectorService: Angular1InjectorService;
readonly testService: any;
constructor(readonly injector: Injector) {
this.angular1InjectorService = this.injector.get(Angular1InjectorService);
this.testService = this.angular1InjectorService.getService('Angular1ExportService');
}
testGetAngular1String() {
console.log('app base service is work!');
return this.testService.outPutAngular1String();
}
}
4. Then the service of the sub app inherits AppBaseService, and obtains the method that exists in angularjs
export class App1RootService extends AppBaseService {
constructor(readonly injector: Injector) {
super(injector);
}
GetLogAngular1String() {
console.log('app1 root service is work!');
return this.testGetAngular1String();
}
}
5. Portal root planet config
this.planet.registerApps([
{
name: 'app1',
hostParent: '#app-host-container',
routerPathPrefix: '/app1',
selector: 'app1-root-container',
resourcePathPrefix: '/static/app1',
preload: settings.app1.preload,
switchMode: settings.app1.switchMode,
loadSerial: true,
manifest: '/static/app1/manifest.json',
scripts: [
'main.js'
],
styles: [
],
}
]);
6. Sub app1 main config
defineApplication('app1', (portalApp: PlanetPortalApplication) => {
return platformBrowserDynamic([
{
provide: PlanetPortalApplication,
useValue: portalApp
},
{
provide: AppRootContext,
useValue: portalApp.data.appRootContext
}
])
.bootstrapModule(App1RootModule)
.then(appModule => {
return appModule;
})
.catch(error => {
console.error(error);
return null;
});
});
Issue related resources:
EXCEPTION: Can't resolve all parameters
Uncaught Error: Can't resolve all parameters for
After upgrade Angular to v8: Can't resolve all parameters for Component:
Intact issue code:
Stackblitz
Github
I have not found an answer, and already have the official github open issue, but hope to get more help
Thanks.

Angular 4 - routerLinks without starting slash get rewritten after uncaught error

This is a follow up to Angular 4 + zonejs: routing stops working after uncaught error
since we had a hard time integration the suggested changes into our project.
The reason for this can be seen in this adapted plunker https://embed.plnkr.co/oUE71KJEk0f1emUuMBp8/ in app.html:
The routerLinks in our project were not prefixed with a slash "/".
This breaks the whole navigation once you navigate to another section after visiting the "Error Component".
All links are being rewritten with the current path, e.g. home.
Adding the slash in the routerLink attributes fixes this behaviour.
Why is that?
And is there some documentation/spec concerning this?
We only found this angular ticket and the api for RouterLink-directive which says
or doesn't begin with a slash, the router will instead look in the children of the current activated route.
But how is this related to what is happening with an uncaught error respectively with the suggested workaround from the previous question?
After an navigation error happens the routing state is restored
this.currentRouterState = storedState;
this.currentUrlTree = storedUrl;
https://github.com/angular/angular/blob/4.1.2/packages/router/src/router.ts#L750-L751
After that within executing createUrlTree the startPosition is obtained:
function findStartingPosition(nav, tree, route) {
if (nav.isAbsolute) { // it will be executed when you use `/` in routeLink
return new Position(tree.root, true, 0);
}
if (route.snapshot._lastPathIndex === -1) { // without '/'
return new Position(route.snapshot._urlSegment, true, 0);
}
...
}
As we can see in code above when you use slash in routeLinks then router will create position based on tree.root which has not changed.
then it is used for creating UrlTree (oldSegmentGroup in code below)
function tree(oldSegmentGroup, newSegmentGroup, urlTree, queryParams, fragment) {
...
if (urlTree.root === oldSegmentGroup) { // will be false after the error
return new UrlTree(newSegmentGroup, qp, fragment);
}
return new UrlTree(replaceSegment(urlTree.root, oldSegmentGroup, newSegmentGroup), qp, fragment);
}
So workaround might be as follows:
We no longer need RouteReuseStrategy.
We store errored state
let erroredUrlTree;
let erroredState;
export class AppModule {
constructor(private router: Router) {
router.events.subscribe(function (e) {
if(e instanceof NavigationError ) {
erroredState = (router as any).currentRouterState;
erroredUrlTree = (router as any).currentUrlTree;
}
});
}
}
and recovery it after error occurs:
#Injectable()
export class MyErrorHandler implements ErrorHandler {
constructor(private inj: Injector) {}
handleError(error: any): void {
console.log('MyErrorHandler: ' + error);
if(erroredUrlTree) {
let router: any = this.inj.get(Router);
router.currentRouterState = erroredState;
router.currentUrlTree = erroredUrlTree;
erroredState = null;
erroredUrlTree = null;
}
}
}
Modified Plunker
It looks terrible but maybe it will help to understand what the problem is

Inject $http into Tour of Heroes Component

I have the Tour of Heroes app running, but I want to extend it to make ajax calls.
I have a WebAPI service that serves up the data (CORS enabled) and have proven it w/ a silly little non Angular client using JQuery $.post and $GetJson ... All was going well...
Here is my hero-details.component.ts file
(happy to include any others that may help...)
import {Component , Input, OnInit } from '#angular/core';
import { ActivatedRoute, Params } from '#angular/router';
import { Location } from '#angular/common';
import { HttpModule } from '#angular/http';
import 'rxjs/add/operator/switchMap';
import { Hero } from './hero';
import { HeroService } from './hero.service';
#Component({
selector: 'hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls : ['./hero-detail.component.css']
})
export class HeroDetailComponent { // implements OnInit {
#Input() hero: Hero;
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
submitted = false;
constructor(
private heroService: HeroService,
private route: ActivatedRoute,
private location: Location,
$http //**--LINE OF INTEREST #2**
) { }
ngOnInit(): void {
this.route.params
.switchMap((params: Params) => this.heroService.getHero(+params['id']))
}
save(): void {
this.heroService.update(this.hero)
.then(() => this.goBack());
}
goBack(): void {
this.location.back();
}
onSubmit() { this.submitted = true; }
callService( ) {
var uri = 'http://localhost:61212/api/heros';
//**// LINE OF INTEREST #1**
$http({
method: 'GET',
url: uri
}).then(function () { alert('success'); }, function () { alert('fail');});
};
}
If I try to compile I get
TS2304: Cannot find '$http'
I can comment the $HTTP call (Line of Interest #1 ) and it compiles, it runs and i do enter the function and hit a breakpoint where i declare and assign the variable "uri". So I am reasonably sure I have the problem isolated.
So I believe, based on hours of googling, that I need to DI the $http object into this component
But when I pass $http into the constructor (LINE OF INTEREST #2) I get the following error when I try to compile
TS7006 Parameter '$http' implicitly has an 'any' type
I have googled this so much Larry and Sergy have asked me to knock it off.
What I have found is $http being passed into controllers, maybe Im missing something, but I can not seem to translate those articles into something that works for this.
1) Am I right that injecting the $http object is what needs to be done
2) What is the syntax?
when I was googling , i was just googling angular and most the articles were for angular1. Thats why I was seeing answers that involved controllers, etc.
angular2 is much different. If you are trying to get off the ground, try searching angular2 instead. at least the articles you run across will be relevant.
if you are using visual studio.. here is a nice link to get you started...
https://jonhilton.net/2016/12/01/fast-track-your-angular-2-and-net-core-web-app-development/

Angular 2 app base initialization

How can I make basic initialization of my data in app. For example if user logged in and press F5 I need to request current user data from server before all queries starts like get user order etc. In Angular 1 we have .run() directive for this case. How can I solve this problem?
There are several ways to do that:
You could execute some requests before bootstrapping your Angular2 application. Such first requests could rely what you save into the local / session storage.
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
http.get('/userdetails').map(res => res.json())
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('userDetails', { useValue: data })
]);
});
See this question for more details:
How to bootstrap an Angular 2 application asynchronously
You could extend the HTTP request to transparently get these data when requests are actually executed. This would be a lazy approach.
#Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, userDetailsService: UserDetailsService) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
console.log('request...');
return this.userDetailsService.getUserDetails().flatMap((userDetails) => {
return super.request(url, options);
});
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
console.log('get...');
return this.userDetailsService.getUserDetails().flatMap((userDetails) => {
return super.get(url, options);
});
}
}
implement the UserDetailsDetails this way:
export class UserDetailsService {
constructor(private http:Http) {
}
getUserDetails() {
if (this.userDetails) {
return Observable.of(this.userDetails);
} else {
return this.http.get(...)
.map(...)
.do(data => {
this.userDetails = data;
// Store in local storage or session storage
});
}
}
and register this CustomHttp class this way:
bootstrap(AppComponent, [HTTP_PROVIDERS,
UserDetailsService,
new Provider(Http, {
useFactory: (backend: XHRBackend,
defaultOptions: RequestOptions,
userDetailsService: UserDetailsService) => new CustomHttp(backend, defaultOptions, userDetailsService),
deps: [XHRBackend, RequestOptions, UserDetailsService]
})
]);
See these questions for more details:
Angular 2 - How to get Observable.throw globally
Cache custom component content in ionic 2
Things could also be done at the level of the router outlet if you use routing. It's possible to implement a custom router-outlet that checks security / user details when a route is activated. I think that it's a little further from your need...
See this question for more details:
Angular 2 Cancelling Route Navigation on UnAuthenticate
You could fetch the current user data before you call Angular2's bootstrap(...)
You could also fire an event (using an Observable for example) to notify other that the logged-in user is now known and initiate further requests only after this event was received.

Categories

Resources