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!