keycloakangular-oauth2-oidc

angular-oauth2-oidc with Keycloak Auth Code Flow Access Token Request is not sent


I am considerably new to Oauth2 paradigm. I am using Angular 14 with Keycloak Identity Server with angular-oauth2-oidc dependency and Auth Code flow grant. I am able to successfully login and getting Auth code back as well, but somehow my application is not making Access Token request to Keycloak. I have gone through few posts on Stack Overflow, Medium and see a few videos on Youtube as well to no avail. I have followed npm angular-oauth2-oidc documentation. See below code snippets:

auth.config.ts

import { AuthConfig } from "angular-oauth2-oidc";

export const oauthConfig:AuthConfig = {
    issuer:'http://localhost:8080/realms/personal-account-manager',
    redirectUri:'http://localhost:4200/user-home',
    // redirectUri:window.location.origin,
    clientId:'personal-account-manager-pkce-client',
    // strictDiscoveryDocumentValidation:false,
    scope:'openid profile',
    responseType:'code',
    showDebugInformation:true,
    tokenEndpoint:'http://localhost:8080/realms/personal-account-manager/protocol/openid-connect/token'
}

auth.guard.ts

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';

export const authGuard: CanActivateFn = (route, state) => {
  const oauthService:OAuthService = inject(OAuthService);
  const router:Router = inject(Router);
  console.log('auth guard called');
  console.log(oauthService);
  if(oauthService.hasValidAccessToken() && oauthService.hasValidIdToken()){
    router.navigate(['/user-home']);
    return true;
  } else {
    console.log('no token');
    router.navigate(['/login']);
    return false;
  }
};

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { authGuard } from './auth.guard';
import { LoginComponent } from './login/login.component';
import { UserHomeComponent } from './user-home/user-home.component';

const routes: Routes = [
  { path: '', redirectTo: 'user-home', pathMatch: 'full' },
  { path: 'login', component: LoginComponent },
  {
    path: 'user-home',
    component: UserHomeComponent,
    canActivate: [authGuard],
  },
  { path: '**', redirectTo: 'user-home', pathMatch: 'full' },
];

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

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'XYZ';

  constructor(private oauthService:OAuthService){
    this.configureAuthService();
  }

  ngOnInit(): void {
    
  }

  configureAuthService(){
    console.log('configuring oauth2');
    this.oauthService.configure(oauthConfig);
    // this.oauthService.tokenValidationHandler = new JwksValidationHandler();
    this.oauthService.loadDiscoveryDocumentAndTryLogin();
  
  }
}

app.component.html

<router-outlet></router-outlet>

login.component.ts

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrl: './login.component.css',
})
export class LoginComponent implements OnInit {
  constructor(private oauthService: OAuthService) {}

  ngOnInit(): void {}

  onLogin() {
    console.log('logging in...');
    this.oauthService.initCodeFlow();
  }
}

login.component.html

<button mat-raised-button (click)="onLogin()">Login</button>

user-home.component.ts

@Component({
  selector: 'app-user-home',
  templateUrl: './user-home.component.html',
  styleUrl: './user-home.component.css'
})
export class UserHomeComponent implements OnInit{
  constructor(private oauthService:OAuthService){}
  ngOnInit(): void {
    console.log('In user-home');
    console.log(this.oauthService.getIdentityClaims());
    
  }
}

user-home.component.html

<p>user-home works!</p>

The issue is I am never redirected to UserHomeComponent. I keep getting redirection to /login path, which is clearly happening as auth-guard doesn't get token. Any help is appreciated!


Solution

  • Turns out there was very simple solution. My UserHomeComponent(protected) was getting loaded before access_token was retrieved. The classic case of synchronous call.

    Also it seems like it is imperative that redirect_uri set in Auth Client is same as the Uri(set in routing module) of component from which the login call is made, in this case it is HomeComponent, only then the second call to retrieve token is made otherwise the flow will be stuck after receiving Auth Code. Please correct me if this is wrong.

    Anyone facing similar issue can refer the solution here.

    Simply put I had to change login function in my AuthService from

    this.oauthService.loadDiscoveryDocumentAndTryLogin()
    

    to

    this.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => {
      if (this.oauthService.hasValidAccessToken()) {
        this.router.navigate(['/user-home']);
      }
    });
    

    I am using local Keycloak Identity Server.

    Here is the flow:

    User lands on HomePage(Has simple Login and Logout buttons) >> On Login, user is redirected to Keycloak login page >> On successful login user is redirected to UserHomeComponent(where user details are displayed).

    Hope this helps!