I'm trying to make an example implicit flow to work with Keycloak as an identity provider.
I've basically followed this tutorial and replaced all the things regarding Okta with Keycloak. When I click on the "login" button (that triggers this.oauthService.initImplicitFlow(); I'm correctly redirected to the Keycloak login page where I can login without any issue.
I'm then redirected back to my application that receives the token and a state in the URL:
http://localhost:4200/#state=sijg...&id_token=eyJhb... (replaced long token with ...)
I've tested the token on jwt.io and it seems to be correct and has the informations that I request (openid profile email).
But, when in the homepage I try to call var claims = this.oauthService.getIdentityClaims();, claims is null. Calling this.oauthService.hasValidIdToken() also returns false.
I'm not 100% sure what should be stored in the sessionStorage, but while debugging I can see that I have a "nonce" stored.
I've tried to compare with your sample application, but can't see anything different. Here's a copy of the most important files:
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { SearchComponent } from './search/search.component';
import { EditComponent } from './edit/edit.component';
import { OAuthModule } from 'angular-oauth2-oidc';
import { HomeComponent } from './home/home.component';
import { AuthGuard } from './shared';
const appRoutes: Routes = [
{ path: 'search', component: SearchComponent, canActivate: [AuthGuard] },
{ path: 'edit/:id', component: EditComponent },
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: '*', redirectTo: 'home' }
];
@NgModule({
declarations: [
AppComponent,
SearchComponent,
EditComponent,
HomeComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
OAuthModule.forRoot(),
RouterModule.forRoot(appRoutes)
],
providers: [AuthGuard],
bootstrap: [AppComponent]
})
export class AppModule { }
app.module.ts
import { Component } from '@angular/core';
import { SearchService } from './shared';
import { Http } from '@angular/http';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
viewProviders: [SearchService]
})
export class AppComponent {
title = 'app';
constructor(private oAuthService: OAuthService) {
this.oAuthService.redirectUri = window.location.origin;
this.oAuthService.clientId = 'demo-app';
this.oAuthService.scope = 'openid profile email';
this.oAuthService.oidc = true;
this.oAuthService.setStorage(sessionStorage);
this.oAuthService.issuer = 'http://[myKeycloakServer]/auth/realms/test';
this.oAuthService.requireHttps = false;
const url = 'http://[myKeycloakServer]/auth/realms/test/.well-known/openid-configuration';
this.oAuthService.loadDiscoveryDocument(url).then((doc) => {
this.oAuthService.tryLogin();
console.debug('discovery succeeded', doc);
});
}
}
auth.guard.service.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { OAuthService } from 'angular-oauth2-oidc';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private oauthService: OAuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
if (this.oauthService.hasValidIdToken()) {
return true;
}
this.router.navigate(['/home']);
return false;
}
}
home.component.ts
import { Component, OnInit } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor(private oauthService: OAuthService) { }
ngOnInit(): void { }
login() {
this.oauthService.initImplicitFlow();
}
logout() {
this.oauthService.logOut();
}
get givenName() {
const claims = this.oauthService.getIdentityClaims();
if (!claims) {
return null;
}
return claims['name'];
}
}
Found the solution, replaced the redirect URI from this.oAuthService.redirectUri = window.location.origin; to this.oAuthService.redirectUri = window.location.origin + '/index.html'; and now it's working.
Not quite sure what the issue is here, why do you have to add the index.html in the URI to make it work?
Hi Gimly,
thanks for this information. The callback-uris have to be registered at server side to prevent that the token is send to an attacker after a successful login. I guess that on serverside the uri is registered with /index.html
Wishes,
Manfred
p. s.: @mraible Nice tutorial. Really like it.
No, on Keycloak I had configured the callback uri with a wildcard to allow any callback uri (for testing purpose) so it's not the Keycloak side which is having an issue.
It's a bit annoying because I'm using .Net MVC server side and it's complicated to make it work with the index.html.
Sorry, I've lost track of this. Did you manage to fix it?
Perhaps it's due to the stricter validations for the config data. Are there errors on the JavaScript console?
@manfredsteyer I had a similar issue as this. I found I had to change my "blank" Angular route from
{ path: '', redirectTo: '/home', pathMatch: 'full' }
to
{ path: '', redirectTo: 'home', pathMatch: 'full' } (no slash)
When I had the slash it seemed like Angular is now eating the hash with the tokens before the auth had a chance to read it. Without the slash, auth is able to process it, and then it goes to the home route as I want.
Sorry, yes I did, it was just my fault, I was adding the index.html myself in the configuration and didn't see it directly. Everything is working fine now. Thanks!
I'm still getting the claims as null all the time and rest is working fine (getting data from service call)
here is my code snippet
` async ngOnInit() {
// Auth Config
this.oauthService.loginUrl = 'https://login.xxxxe.com/xxxx88bf-8xxx-4xxf-9xxb-2d7xxxxxb47/oauth2/authorize';
this.oauthService.clientId = '0xxxxxxc-dd5e-4xx2-be6d-xxxxcxxxxxxx';
this.oauthService.resource = 'https://xxxsvcapi.azurewebsites.net/';
this.oauthService.redirectUri = window.location.origin + '/';
this.oauthService.scope = 'openid';
this.oauthService.tryLogin({});
if (!this.oauthService.getAccessToken()) { await this.oauthService.initImplicitFlow(); }
this.commonService.bearerToken = this.oauthService.getAccessToken();
const claims = this.oauthService.getIdentityClaims();
if (claims) {
console.log('claim ' + claims['name']);
}
}`
Any help will be grateful.
@subhashkonda Did you solve your problem?
I have the same issue with my azure active directory.
Problem solved. I did not await that the tryLogin method succeeded. After fixing this I got the claims.
Great Library!
I also encounter this issue lately. I use the implicit flow with JWT, everything else works fine. I wonder where should I call the tryLogin() and where to add the "await"? My current initialize code is like this:
this.oauthService.loginUrl = 'http://localhost:8081/auth/oauth/authorize';
this.oauthService.redirectUri = 'http://localhost:4200/service';
this.oauthService.clientId = 'xxx';
this.oauthService.scope = 'user_info';
this.oauthService.setStorage(sessionStorage);
this.oauthService.oidc = false;
this.oauthService.requireHttps = false;
this.oauthService.tryLogin({});
and the login code is like this:
this.oauthService.initImplicitFlow();
this.oauthService.initImplicitFlow();
const claims = this.oauthService.getIdentityClaims();
console.log('claim ' + claims['name']);
@blumatixgs do you mind to share your working code? Thanks in advance.
@a7744hsc this is our code
private async init() {
console.log("init");
await this.ConfigureAuth();
await this.authService.tryLogin({});
if (!this.authService.getAccessToken()) {
await this.authService.initImplicitFlow();
}
const claim: any = this.authService.getIdentityClaims();
console.log ("claim", claim);
if (claim) {
this.username = claim.name;
this.isLoggedIn = true;
console.log("TOKEN: " + this.authService.getAccessToken());
}
}
I think you have misunderstood how to use this library.
So the tryLogin method returns a Promise so you can get your token in the scope of that callback.
Have a look at this:
https://github.com/manfredsteyer/angular-oauth2-oidc/blob/dc3843228721c8905b8720b46cd5c168d5401bdc/angular-oauth2-oidc/md/callback-after-login.md
Also see this for configuring the flow:
https://github.com/manfredsteyer/angular-oauth2-oidc/blob/dc3843228721c8905b8720b46cd5c168d5401bdc/angular-oauth2-oidc/md/implicit-flow-config-no-discovery.md
You should call this.authService.initImplicitFlow() from some login button.
Maybe also have look at this:
https://github.com/manfredsteyer/angular-oauth2-oidc/pull/195#issuecomment-352855051
The Authorization Code Flow is in a pull request and still not merged, but the concept here is the same as for the Implicit flow
@bechhansen thanks for your clarification, we will revise it.
@blumatixgs Thanks for your sharing.
I have checked the source code, it seems like I can't use "getIdentityClaims" for the oauth2 authentication.
@a7744hsc We are using OIDC, our scope ist set to:
scope ="openid profile";
The returned claim looks like this:
aio: "xxxx"
amr: Array [ "pwd" ]
at_hash: "xxxxx"
aud: "xxxx"
email: "[email protected]"
exp: 1517331145
iat: 1517327245
idp: "https://sts.windows.net/xxxx/"
ipaddr: "8.8.6.3"
iss: "https://sts.windows.net/xxxx/"
name: "xxxx"
nbf: 12345
nonce: "xxxxx"
oid: "xxxx"
sub: "xxxx"
tid: "xxxx"
unique_name: "xx"
uti: "xxxx"
ver: "1.0"
What was the fixes for this? I have tried everything and It does not seem to work..
`
I'm still getting this problem as well
First, MS does not allow CORS, so you can't use the discovery document, you have to set the issuer and the jwks in the AuthConfig manually.
Based on https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration,
I explicitly set my issuer in the config as https://login.microsoftonline.com/{tenantId}/v2.0 but
the AzureAd claim returns the issuer as https://sts.windows.net/{tenantId}/
The library has a check to evaluate they match. Once I set my issuer to https://sts.windows.net/{tenantId}/ everything worked.
Most helpful comment
@manfredsteyer I had a similar issue as this. I found I had to change my "blank" Angular route from
{ path: '', redirectTo: '/home', pathMatch: 'full' }to
{ path: '', redirectTo: 'home', pathMatch: 'full' }(no slash)When I had the slash it seemed like Angular is now eating the hash with the tokens before the auth had a chance to read it. Without the slash, auth is able to process it, and then it goes to the home route as I want.