1

My application is made of two separated projects: a .NET core back-end and an Angular front-end.

I have successfully added windows AD authentication and I am getting user info inside my Angular application but only after refreshing the page.

At first page opening, my user is null

ERROR TypeError: Cannot read property 'name' of null

After a refresh, I am able to get the user info. What is actually happening and how shold I fix this issue?

public Task < AdUser > GetAdUser(Guid guid) {
  return Task.Run(() => {
    try {
      PrincipalContext context = new PrincipalContext(ContextType.Domain);
      UserPrincipal principal = new UserPrincipal(context);

      if (context != null) {
        principal = UserPrincipal.FindByIdentity(context, IdentityType.Guid,
          guid.ToString());
      }

      return AdUser.CastToAdUser(principal);
    } catch (Exception ex) {
      throw new Exception("Error retrieving AD User", ex);
    }
  });
}

identity.service.ts

getCurrentUser() {
    return this.http.get(this.apiUrl + '/identity/GetAdUser', { withCredentials: true });
}

app.component.rs

this.identityService.getCurrentUser()
.subscribe(res => {
localStorage.setItem('user', JSON.stringify(res));
});

top-navigation.component.ts

user = JSON.parse(localStorage.getItem('user'));

Finally, I am getting the user data inside my top-navigation component because I need the first and last name to be displayed inside of it.

EDIT: added new code

app-routing.module.ts

    const appRoutes: Routes = [
   {
    path: 'home',
    component: HomeComponent,
    data: { title: 'Home' },
    canActivate: [AuthGuardService]
  },
  {
    path: 'history',
    component: HistoryComponent,
    data: { title: 'History' }
  },
  {
    path: '404',
    component: NotFoundErrorComponent,
    data: { title: 'Error 404' }
  },
  {
    path: '',
    component: HomeComponent,
    pathMatch: 'full'
  },
  {
    path: '**',
    redirectTo: '/404',
    pathMatch: 'full',
    data: { title: 'Error 404' }
  }
];

@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

app.component.ts

    @Component({
     selector: 'app-root',
     templateUrl: './app.component.html',
     styleUrls: ['./app.component.scss']
    })
    export class AppComponent implements OnInit {
     title = 'Home';

    constructor(private titleService: Title,
              private router: Router,
              private activatedRoute: ActivatedRoute { }

    ngOnInit() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      map(() => this.activatedRoute),
      map((route) => {
        while (route.firstChild) { route = route.firstChild; }
        return route;
      }),
      filter((route) => route.outlet === 'primary'),
      mergeMap((route) => route.data))
      .subscribe((event) => this.titleService.setTitle(event.title));
     }
     }

top-navigation.component.ts

    export class TopNavigationComponent implements OnInit {
      constructor(private router: Router) { }

      user;

      ngOnInit() {
       this.user = JSON.parse(localStorage.getItem('user'));
      }
     }

EDIT: final solution

app-routing.module.ts

const appRoutes: Routes = [
{
path: '',
component: TopNavComponent,
resolve: { userData: IdentityResolverService },
children: [
  {
    path: 'home',
    component: HomeComponent,
    data: { title: 'Home' },
    canActivate: [AuthGuardService]
  },
  {
    path: 'history',
    component: HistoryComponent,
    data: { title: 'History' },
    canActivate: [AuthGuardService]
  },
  {
    path: '404',
    component: NotFoundErrorComponent,
    data: { title: 'Error 404' },
    canActivate: [AuthGuardService]
  },
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full',
    canActivate: [AuthGuardService]
  },
  {
    path: '**',
    redirectTo: '/404',
    pathMatch: 'full',
    data: { title: 'Error 404' },
    canActivate: [AuthGuardService]
  }
]
};
]
1
  • Rephrased and formatted code snippets Commented Oct 24, 2019 at 15:39

1 Answer 1

1

Ok, I'll try to explain how all this works. Your problem is that the getCurrentUser from your component executes after the localStorage is trying to get the data.

If your AppComponent is the root component of your application ad the top-navigation component is declared in the app component html, then you need to make sure the data is there before the AppComponent starts.

So, what happens is (a possible timeline as the async nature of javascript means that at times some steps will execute in a different order):

  1. The application gets routed to your core route
  2. The constructor of AppComponent executes
  3. The constructor of TopNavigationComponent executes
  4. The getCurrentUser() http call starts
  5. The TopNavigationComponent tries to get the value from localStorage (Better to use SessionStorage for security reasons) and fails
  6. The getCurrentUser() http call ends and the AppComponent sets the localStorage value
  7. You refresh. When you reach the step 5. the local storage has been set from step 6.

The best solution would be to use a guard. That is more valid for your case if you need to use ADUser as a security feature. If the guard fails, then you will not transition to the AppComponent.

Declare a Guard, and if the data is there, then proceed to the app component and the guard will make sure that you have the ADUser data in the local storage (or session storage).

The resolver (another possible solution) will always run before the AppComponent is loaded and will not block, thus making sure that you have the data if the application has responded.

More information about guards and resolvers here

Take a look at the following guard:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private adUserService: ADUserService, 
    private router: Router
  ) {}

  canActivate(next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> {
    // We will return a true if the user is logged in
    // or false otherwise
    return this.adUserService.login().pipe(
      map((x) => {
        // Since my service returned succesfully I will set the
        // locale storage with the appropriate data
        localStorage.setItem('user', JSON.stringify(x));
        // I will also return true to let the router module
        // know that it can proceed
        return true;
      }),
      catchError(() => {
        // If there was an error, I will return false, so the
        // router module will not allow the transition
        // if I want to, I can add the router module to transition
        // to a login page
        return of(false)
      })
    );
  }
}

You can check the full solution here: stackblitz example

To see the locale storage in action, try the https://so-angular-route-guards-locale-storage.stackblitz.io/user url.

Sign up to request clarification or add additional context in comments.

12 Comments

thanks for provided solution. I have setup the Guard service where I am checking is user object null, but I am not sure to understand what to do if its null... You said " and if the data is there, then proceed to the app component and the guard will make sure that you have the ADUser data in the local storage", how to proceed and not to proceed to main app component... Hope you understand what do I mean by this.
I sure do. Check the stackblitz url for the full solution. I did not include the top navigation. Be sure to vote for the answer if it is helpful to you.
sorry but I am having some issues with this, even your stackblitz demo is working fine... inside my code, my app is always entering into CanActivate and trying to get the data this.adUserService.login(), but for some reason, my top-navigation is trying to render data and of course user is null. What can be the reason for that? Have in mind that I am calling "this.user = JSON.parse(localStorage.getItem('user'))" under ngOnInit inside top-navigation.component.ts but obviously its null... Do I also need to use the Resolver?
Can you please share your router configuration and the AppComponent/TopNavigation in your code?
I have updated my question with the code you asked for.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.