This article shows how an Angular client could implement a login for a SPA application using Google Identity Platform OpenID. The Angular application uses the npm package angular-auth-oidc-client to implement the OpenID Connect Implicit Flow to connect with the google identity platform.
Code: https://github.com/damienbod/angular-auth-oidc-sample-google-openid
History
2020-05-03 Updated to OIDC lib version 11.0.0, Angular 9.1.4, ASP.NET Core 3.1
2017-07-09 Updated to version 1.1.4, new configuration
Setting up Google Identity Platform
The Google Identity Platform provides good documentation on how to set up its OpenID Connect implementation.
You need to login into google using a gmail account.
https://accounts.google.com
Now open the OpenID Connect google documentation page
https://developers.google.com/identity/protocols/OpenIDConnect
Open the credentials page provided as a link.
https://console.developers.google.com/apis/credentials
Create new credentials for your application, select OAuth Client ID in the drop down:
Select a web application and configure the parameters to match your client application URLs.
Implementing the Angular OpenID Connect client
The client application is implemtented using ASP.NET Core and Angular.
The npm package angular-auth-oidc-client is used to connect to the OpenID server. The package can be added to the package.json file in the dependencies.
"dependencies": { ... "angular-auth-oidc-client": "11.0.0" },
Now the AuthModule, OidcSecurityService, AuthConfiguration can be imported. The AuthModule.forRoot() is used and added to the root module imports, the OidcSecurityService is added to the providers and the AuthConfiguration is the configuration class which is used to set up the OpenID Connect Implicit Flow.
import { HttpClientModule } from '@angular/common/http'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { AuthModule, LogLevel, OidcConfigService, OidcSecurityService } from 'angular-auth-oidc-client'; import { AppComponent } from './app.component'; import { routing } from './app.routes'; import { AutoLoginComponent } from './auto-login/auto-login.component'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { HomeComponent } from './home/home.component'; import { NavigationComponent } from './navigation/navigation.component'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; export function configureAuth(oidcConfigService: OidcConfigService) { return () => oidcConfigService.withConfig({ stsServer: 'https://accounts.google.com', redirectUrl: window.location.origin, clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', responseType: 'id_token token', scope: 'openid email profile', triggerAuthorizationResultEvent: true, postLogoutRedirectUri: window.location.origin + '/unauthorized', startCheckSession: false, silentRenew: false, silentRenewUrl: window.location.origin + '/silent-renew.html', postLoginRoute: '/home', forbiddenRoute: '/forbidden', unauthorizedRoute: '/unauthorized', logLevel: LogLevel.Debug, historyCleanupOff: true, // iss_validation_off: false // disable_iat_offset_validation: true }); } @NgModule({ imports: [BrowserModule, FormsModule, routing, HttpClientModule, AuthModule.forRoot()], declarations: [AppComponent, ForbiddenComponent, HomeComponent, AutoLoginComponent, NavigationComponent, UnauthorizedComponent], providers: [ OidcSecurityService, OidcConfigService, { provide: APP_INITIALIZER, useFactory: configureAuth, deps: [OidcConfigService], multi: true, }, ], bootstrap: [AppComponent], }) export class AppModule {}
The AuthConfiguration class is used to configure the module.
stsServer
This is the URL where the STS server is located. We use https://accounts.google.com in this example.
redirectUrl
This is the redirect_url which was configured on the google client ID on the server.
clientId
The client_id must match the Client ID for Web application which was configured on the google server.
responseType
This must be ‘id_token token’ or ‘id_token’. If you want to use the user service, or access data using using APIs, you must use the ‘id_token token’ configuration. This is the OpenID Connect Implicit Flow. The possible values are defined in the well known configuration URL from the OpenID Connect server.
scope
Scope which are used by the client. The openid must be defined: ‘openid email profile’
postLogoutRedirectUri
Url after a server logout if using the end session API. This is not supported by google OpenID.
startChecksession
Checks the session using OpenID session management. Not supported by google OpenID
silentRenew
Renews the client tokens, once the token_id expires.
startupRoute
Angular route after a successful login.
forbiddenRoute
HTTP 403
unauthorizedRoute
HTTP 401
logLevel
Logs all module warnings to the console.
export function configureAuth(oidcConfigService: OidcConfigService) { return () => oidcConfigService.withConfig({ stsServer: 'https://accounts.google.com', redirectUrl: window.location.origin, clientId: '188968487735-b1hh7k87nkkh6vv84548sinju2kpr7gn.apps.googleusercontent.com', responseType: 'id_token token', scope: 'openid email profile', triggerAuthorizationResultEvent: true, postLogoutRedirectUri: window.location.origin + '/unauthorized', startCheckSession: false, silentRenew: false, silentRenewUrl: window.location.origin + '/silent-renew.html', postLoginRoute: '/home', forbiddenRoute: '/forbidden', unauthorizedRoute: '/unauthorized', logLevel: LogLevel.Debug, historyCleanupOff: true, // iss_validation_off: false // disable_iat_offset_validation: true }); }
The following json is the actual configuration for the google well known configuration. What’s really interesting is that the end session endpoint is not supported, which is strange I think. It’s also interesting to see that the response_types_supported supports a type which is not supported “token id_token”, this should be “id_token token”.
See: http://openid.net/specs/openid-connect-core-1_0.html
{ "issuer": "https://accounts.google.com", "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", "token_endpoint": "https://www.googleapis.com/oauth2/v4/token", "userinfo_endpoint": "https://www.googleapis.com/oauth2/v3/userinfo", "revocation_endpoint": "https://accounts.google.com/o/oauth2/revoke", "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs", "response_types_supported": [ "code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none" ], "subject_types_supported": [ "public" ], "id_token_signing_alg_values_supported": [ "RS256" ], "scopes_supported": [ "openid", "email", "profile" ], "token_endpoint_auth_methods_supported": [ "client_secret_post", "client_secret_basic" ], "claims_supported": [ "aud", "email", "email_verified", "exp", "family_name", "given_name", "iat", "iss", "locale", "name", "picture", "sub" ], "code_challenge_methods_supported": [ "plain", "S256" ] }
The AppComponent implements the authorize and the authorizedCallback functions from the OidcSecurityService provider.
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { OidcSecurityService } from 'angular-auth-oidc-client'; @Component({ selector: 'app-root', templateUrl: 'app.component.html', }) export class AppComponent implements OnInit, OnDestroy { constructor(public oidcSecurityService: OidcSecurityService, private router: Router) {} ngOnInit() { this.oidcSecurityService .checkAuth() .subscribe((isAuthenticated) => { if (!isAuthenticated) { if ('/autologin' !== window.location.pathname) { this.write('redirect', window.location.pathname); this.router.navigate(['/autologin']); } } if (isAuthenticated) { this.navigateToStoredEndpoint(); } }); } ngOnDestroy(): void {} login() { console.log('start login'); this.oidcSecurityService.authorize(); } refreshSession() { console.log('start refreshSession'); this.oidcSecurityService.authorize(); } logout() { console.log('start logoff'); this.oidcSecurityService.logoff(); } private navigateToStoredEndpoint() { const path = this.read('redirect'); if (this.router.url === path) { return; } if (path.toString().includes('/unauthorized')) { this.router.navigate(['/']); } else { this.router.navigate([path]); } } private read(key: string): any { const data = localStorage.getItem(key); if (data != null) { return JSON.parse(data); } return; } private write(key: string, value: any): void { localStorage.setItem(key, JSON.stringify(value)); } }
Running the application
Start the application using IIS Express in Visual Studio 2017. This starts with https://localhost:44386 which is configured in the launch settings file. If you use a differnt URL, you need to change this in the client application and also the servers client credentials configuration.
Login then with your gmail.
And you are redirected back to the SPA.
Links:
https://www.npmjs.com/package/angular-auth-oidc-client
https://developers.google.com/identity/protocols/OpenIDConnect
Damien, I noticed that you say Google block the downloading of the configuration of the OIDC configuration from the .well-known/openid-configuration endpoint, and therefore the downloading of it, and saving of it to a local file.
For providers that does support this, will your library automatically download the configuration from the .well-known/openid-configuration endpoint – ie. we do not have to download it and save it locally first?
Hi Jerrie, at present, I do this manually and add it to the using project. It is not part of the npm lib. The lib allows you to configure the src if required.
Google blocks it for a browser application; CORS. You can download it from a desktop application or direct in the browser.
Greetings Damien
[…] Angular OIDC OAuth2 client with Google Identity Platform […]
Hi Damien, I noticed that your example says version 0.0.8, and I am getting 1.1.4. Could that be the reason that all configuration items in module constructor are throwing an error “Cannot assign to ” because it is a constant or a read-only property.”.
Of course, it could be something else as well… Should I contact package author 😉
Hi Felix, sorry yes I will update the blog. Version 1.1.4 is configured differently now. check the github repo with this blog, it has been updated already.
yes, thank you, I noticed (after I posted). Looking through repo explanation, it goes to a generic Identity Provider (like, I suppose, IdentityServer 4) – not specifically, Google. Nothing wrong – but more complicated for a first attempt 🙂
FWIW, as soon as I inject OidcSecurityService the module code from repo gives me “Unhandled Promise rejection: No provider for AuthConfiguration! ; Zone: ; Task: Promise.then ; Value: Error: No provider for AuthConfiguration!”, although there are no references in AuthConfiguration in *my*code…
Hi Felix
I checked the example and it works. Have you configured the app.module correctly:
https://github.com/damienbod/angular-auth-oidc-sample-google-openid/blob/master/src/AngularClient/angularApp/app/app.module.ts
Greetings Damien
Looks like the problem was that I imported AuthModule, rather than AuthModule.forRoot(). And I see that you updated the post 🙂 🙂
Thank you!
cool, great that it’s working for you 🙂
[…] Angular OIDC OAuth2 client with Google Identity Platform … – This article shows how an Angular client could implement a login for a SPA application using Google Identity Platform OpenID. The Angular application uses the npm … […]
Do you have an example for Azure AD??
yep
https://github.com/damienbod/angular-auth-oidc-sample-google-openid
https://github.com/HWouters/ad-b2c-oidc-angular
Using the example attached, i receive the error:
AADSTS50001: Resource identifier is not provided.
I set de Resource property, but it is not added to the request url, when I add manually”&resource=myresource” to the url request, it works perfectly. But it seems that is not added by the library.
Hi Damien, great library and great sample code.
I’ve got the sample code working (using 3.0.3 version) but after authenticating the user the access token and id token are still present in the url (i.e. anyone can copy and paste that url and get in). Is that supposed to be the normal behavior? Am I doing something wrong?
Thanks
from 7/11/2017 stopped working example. Began to issue error 400. “That’s an error. Error: redirect_uri_mismatch”.
It’s a pity, but I do not have enough skill to understand why. Tried to substitute their registration data, still an error.
I’m sorry, it’s already working. Half a day did not work. Today launched with its registration data, and everything turned out.
I only modified with client_id which I obtained on google developers page. But I always meet the error 400. “That’s an error. Error: redirect_uri_mismatch”, and still unsolved. Need some help.
Hi Wendy
You have to configure you google application to match the configuration sent in the Angular app. The redirect uri from the Angular app does not match what is configured in the google Application configuration. Update the google account application to accpet the redirect and it will work.
Greetings Damien
Hi Damien,
I have a problem with all your angular project example, when loading the project in visual studio 2017 and launching the debug, I always stumble on a blank page with “This is server routing, not angular2 routing”
I think something somewhere is missing but i don’t know what
I have an angular application that is using the template available with the .net core 2.0 and it’s working fine
Hi, Damien
I have update the google account application and still have the 400 error. So I have some question about redirect url. Can redirect uri be direct visited locally?Does the configuration of uri have special requirements? The redirect uri of my google application is same with you when I configure.
cool thanks
Hi, Damien
I have solved the bug. Thank you for job and share!