Skip to content
This repository has been archived by the owner on Mar 3, 2022. It is now read-only.

login_required error in web browser console when calling signinRedirectCallback #1393

Open
daxnet opened this issue Jun 12, 2021 · 0 comments

Comments

@daxnet
Copy link

daxnet commented Jun 12, 2021

I'm using ASP.NET Core IdentityServer4 as the IdP and oidc-client library in my Angular project to integrate the id service. However, after user login, following error occurred twice in the web browser console:

VM10 vendor.js:12638 ERROR Error: Uncaught (in promise): ErrorResponse: login_required
ErrorResponse: login_required
    at new e (VM10 vendor.js:49237)
    at t [as _processSigninParams] (VM10 vendor.js:49237)
    at t [as validateSigninResponse] (VM10 vendor.js:49237)
    at VM10 vendor.js:49237
    at ZoneDelegate.invoke (VM7 polyfills.js:10689)
    at Object.onInvoke (VM10 vendor.js:34843)
    at ZoneDelegate.invoke (VM7 polyfills.js:10688)
    at Zone.run (VM7 polyfills.js:10451)
    at VM7 polyfills.js:11593
    at ZoneDelegate.invokeTask (VM7 polyfills.js:10723)
    at resolvePromise (VM7 polyfills.js:11530)
    at VM7 polyfills.js:11437
    at asyncGeneratorStep (VM10 vendor.js:61578)
    at _throw (VM10 vendor.js:61601)
    at ZoneDelegate.invoke (VM7 polyfills.js:10689)
    at Object.onInvoke (VM10 vendor.js:34843)
    at ZoneDelegate.invoke (VM7 polyfills.js:10688)
    at Zone.run (VM7 polyfills.js:10451)
    at VM7 polyfills.js:11593
    at ZoneDelegate.invokeTask (VM7 polyfills.js:10723)

My ASP.NET Core IdentityServer4 has the following client configuration:

public static IEnumerable<Client> GetClients() =>
    new[]
    {
        new Client
        {
            RequireConsent = false,
            ClientId = "web",
            ClientName = "Abacuza Administrator",
            AllowedGrantTypes = GrantTypes.Implicit,
            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                IdentityServerConstants.StandardScopes.Email,
                "roles",
                "api"
            },
            RedirectUris = { "http://localhost:4200/auth-callback" },
            PostLogoutRedirectUris = {"http://localhost:4200/"},
            AllowedCorsOrigins = {"http://localhost:4200"},
            AllowAccessTokensViaBrowser = true,
            AlwaysSendClientClaims = true,
            AlwaysIncludeUserClaimsInIdToken = true,
            AccessTokenLifetime = 3600
        }
    };

In my Angular front-end app, I created the following AuthService:

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  private loginChangedSubject = new Subject<boolean>();
  private userManager : UserManager;
  private user: User | null = null;

  public loginChanged = this.loginChangedSubject.asObservable();

  constructor() {
    this.userManager  = new UserManager(this.getUserManagerSettings());
  }

  public async login() {
    await this.userManager.signinRedirect();
  }

  public async logout() {
    await this.userManager.signoutRedirect();
  }

  public get currentUser(): User | null {
    return this.user;
  }

  public async completeAuthentication(): Promise<void> {
    const user = await this.userManager.signinRedirectCallback();
    if (this.user !== user) {
      this.user = user;
      this.loginChangedSubject.next(this.checkUser(user));
    }
  }

  public isAuthenticated = (): Promise<boolean> => {
    return this.userManager.getUser()
      .then(user => {
        if (this.user !== user) {
          this.loginChangedSubject.next(this.checkUser(user));
          this.user = user;
        }
        return this.checkUser(user);
      });
  }

  public userHasRole(role: string): boolean {
    const roles: string[] = this.user == null ? [] : this.user.profile.role;
    return roles.findIndex(item => item === role) >= 0;
  }

  public get isAdmin(): boolean {
    return this.userHasRole('admin');
  }

  private checkUser = (user: User | null): boolean => !!user && !user.expired;

  private getUserManagerSettings(): UserManagerSettings {
    return {
      authority: 'http://localhost:9050/',
      client_id: 'web',
      redirect_uri: 'http://localhost:4200/auth-callback',
      post_logout_redirect_uri: 'http://localhost:4200/',
      response_type: 'id_token token',
      scope: 'openid profile email roles api',
      filterProtocolClaims: true,
      loadUserInfo: true,
      automaticSilentRenew: true,
      revokeAccessTokenOnSignout: true
    };
  }
}

Since I configured the redirect_uri to be under the route auth-callback, I then created the following AuthCallback component:

@Component({
  selector: 'app-auth-callback',
  templateUrl: './auth-callback.component.html',
  styleUrls: ['./auth-callback.component.scss']
})
export class AuthCallbackComponent implements OnInit {
 
 constructor(private authService: AuthService,
    private router: Router,
    private route: ActivatedRoute) { }
 
 async ngOnInit() {
    await this.authService.completeAuthentication();
    this.router.navigate(['/'], { replaceUrl: true });
  }
}

And certainly I put it into the route config such that auth-callback could be redirected to the AuthCallbackComponent:

{ path: 'auth-callback', component: AuthCallbackComponent },

Finally, I used the AuthService in one of my app component:

@Component({
  selector: 'app-main-side-bar',
  templateUrl: './main-side-bar.component.html',
  styleUrls: ['./main-side-bar.component.scss']
})
export class MainSideBarComponent implements OnInit {

  public user: User | null = null;
  public isAdmin: boolean = false;

  constructor (private authService: AuthService) {
    this.authService.loginChanged
      .subscribe(authenticated => {
        if (authenticated) {
          this.user = this.authService.currentUser;
          this.isAdmin = this.authService.isAdmin;
        } else {
          this.user = null;
        }
      });
  }

  ngOnInit(): void {
    this.authService.isAuthenticated()
      .then(_ => {
        this.user = this.authService.currentUser;
        this.isAdmin = this.authService.isAdmin;
      });
  }
}

In the above code, when authService.completeAuthentication() is called in the AuthCallbackComponent, it will then call the signinRedirectCallback method, this is where the error was reported. I just tried several approaches to refine the code flow, but looks like none worked. Did I miss anything? Could someone please help on this?

Thanks in advance!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

1 participant