I have added the relevant code to integrate Keycloak with Angular. When I go to certain pages I get redirected to Keycloak to log in. After I log in I can successfully browse around using Single Sign-on. However, the issue arises with my get requests.
I was learning from a tutorial online. Which asks the viewer to Crete the following user service:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UserService {
apiUrl = 'https://api.github.com/users';
constructor(private http: HttpClient) { }
getUsers() {
return this.http
.get(`${this.apiUrl}?per_page=10`);
}
getUser(username: string) {
return this.http
.get(`${this.apiUrl}/${username}`);
}
}
Then after that we call this and use the following:
import { Component, OnInit } from '@angular/core';
import { UserService } from 'src/app/core/user.service';
@Component({
selector: 'app-user-list',
template: `
<section class="section">
<div class="container">
<div class="columns is-multiline" *ngIf="users">
<div class="column is-4" *ngFor="let user of users | async">
<div class="card">
<div class="card-content">
<a routerLink="/users/{{ user.login }}">
{{ user.login }}
</a>
</div>
</div>
</div>
</div>
</div>
</section>
`,
styles: [
]
})
export class UserListComponent implements OnInit {
users: any;
constructor(private userService: UserService) {
}
ngOnInit(): void {
this. users = this.userService.getUsers();
}
}
Now it works perfectly fine without AuthGuard. However, once I add AuthGuard. Then I get the following error:
HttpErrorResponse {headers: HttpHeaders, status: 401, statusText: 'OK', url: 'https://api.github.com/users?per_page=10', ok: false, …}
error:
documentation_url: "https://docs.github.com/rest"
message: "Bad credentials"
[[Prototype]]: Object
headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure response for https://api.github.com/users?per_page=10: 401 OK"
name: "HttpErrorResponse"
ok: false
status: 401
statusText: "OK"
url: "https://api.github.com/users?per_page=10"
[[Prototype]]: HttpResponseBase
I am deeply confused as to why I'm getting this 401 response after login. No credentials are needed to access to call this API. I'd be very appreciative if you'd provide any information or guidance on how I can solve this deeply troubling problem. I've read it may have something to do with Authorisation bearer tokens being added. However, I'll need to ping many different services and how can I stop this from affecting: i) External API calls ii) Internal API calls to for example and API gateway
EDIT: To answer Harsh Singla's question I'm adding the following code of app.module.ts:
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ContactComponent } from './contact/contact.component';
import { CoreModule } from './core/core.module';
import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular';
import { initializeKeycloak } from './utility/app.init';
import { AuthGuard } from './utility/app.guard';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ContactComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule,
CoreModule,
KeycloakAngularModule
],
providers: [
AuthGuard,
{
provide: APP_INITIALIZER,
useFactory: initializeKeycloak,
multi: true,
deps: [KeycloakService],
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
The following code is the AuthGuard code:
import { Injectable } from '@angular/core';
import {
ActivatedRouteSnapshot,
Router,
RouterStateSnapshot
} from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
@Injectable({
providedIn: 'root'
})
export class AuthGuard extends KeycloakAuthGuard {
constructor(
protected override readonly router: Router,
protected readonly keycloak: KeycloakService
) {
super(router, keycloak);
}
public async isAccessAllowed(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Promise<boolean> {
// Force the user to log in if currently unauthenticated.
if (!this.authenticated) {
await this.keycloak.login({
redirectUri: window.location.origin + state.url
});
}
// Get the roles required from the route.
const requiredRoles = route.data['roles'];
// Allow the user to proceed if no additional roles are required to access the route.
if (!(requiredRoles instanceof Array) || requiredRoles.length === 0) {
return true;
}
// Allow the user to proceed if all the required roles are present.
return requiredRoles.every((role) => this.roles.includes(role));
}
}
The initialisation is of Keycloak is as follows:
import { KeycloakService } from 'keycloak-angular';
export function initializeKeycloak(
keycloak: KeycloakService
) {
return () =>
keycloak.init({
config: {
url: 'http://localhost:8080',
realm: '[REALM NAME]',
clientId: '[CLIENT ID NAME]',
},
initOptions: {
},
bearerExcludedUrls: ['/assets', 'https://api.github.com/users?per_page=10'],
});
}
I am more than willing to provide more details. Thank you for your feedback so far Harsh.
Sincerely yours,
Shuaib