It's time to hop into Angular. From Solution Explorer, right-click to the /ClientApp/app/components/login/ folder and add a new login.externalproviders.component.ts file with the following content:
import { Component, Inject, OnInit, NgZone, PLATFORM_ID } from "@angular/core";
import { isPlatformBrowser } from '@angular/common';
import { HttpClient } from "@angular/common/http";
import { Router } from "@angular/router";
import { AuthService } from '../../services/auth.service';
declare var window: any;
@Component({
selector: "login-externalproviders",
templateUrl: "./login.externalproviders.component.html"
})
export class LoginExternalProvidersComponent implements OnInit {
externalProviderWindow :any;
constructor(
private http: HttpClient,
private router: Router,
private authService: AuthService,
// inject the local zone
private zone: NgZone,
@Inject(PLATFORM_ID) private platformId: any,
@Inject('BASE_URL') private baseUrl: string) {
}
ngOnInit() {
if (!isPlatformBrowser(this.platformId)) {
return;
}
// close previously opened windows (if any)
this.closePopUpWindow();
// instantiate the externalProviderLogin function
// (if it doesn't exist already)
var self = this;
if (!window.externalProviderLogin) {
window.externalProviderLogin = function (auth:
TokenResponse) {
self.zone.run(() => {
console.log("External Login successful!");
self.authService.setAuth(auth);
self.router.navigate([""]);
});
}
}
}
closePopUpWindow() {
if (this.externalProviderWindow) {
this.externalProviderWindow.close();
}
this.externalProviderWindow = null;
}
callExternalLogin(providerName: string) {
if (!isPlatformBrowser(this.platformId)) {
return;
}
var url = this.baseUrl + "api/Token/ExternalLogin/" +
providerName;
// minimalistic mobile devices support
var w = (screen.width >= 1050) ? 1050 : screen.width;
var h = (screen.height >= 550) ? 550 : screen.height;
var params = "toolbar=yes,scrollbars=yes,resizable=yes,width="
+ w + ", height=" + h;
// close previously opened windows (if any)
this.closePopUpWindow();
this.externalProviderWindow = window.open(url,
"ExternalProvider", params, false);
}
}
Also, here's the login.externalproviders.component.html template file:
<button class="btn btn-sm btn-primary btn-block"
type="submit"
(click)="callExternalLogin('Facebook')">
<span class="glyphicon glyphicon-log-in"></span>
Login with Facebook
</button>
As we can see, there are many relevant differences between this component and the login.facebook.component.ts that we added when we implemented our implicit flow. Let's try to enumerate them:
- Instead of relying on an SDK UI button, we're back to a homemade, Angular-driven, Bootstrap-styled button with a generic callExternalLogin event handler method. That's not a surprise, we already know that the explicit flow doesn't require a client-side SDK, since the OAuth2 cycle will be entirely handled with the server-side API endpoints we added to the TokenController early on. The great stuff here is that the event handler, just like these APIs, can act as a common interface with multiple external providers. Theoretically, we can add a Login with Google and/or a Login with Twitter button with the same identical approach.
- By looking at the callExternalLogin function itself, we can see that it just opens a pop-up window using plain JavaScript with an HTTP request call to the ExternalLogin action method. That's also expected; we knew we had to work around such tasks by ourselves--we addressed it as one of the most relevant flaws of the explicit flow approach. That said, we did what we could to give a decent size to that popup for desktop environments, yet it will hardly work well in mobile browsers; that's definitely an open issue that needs to be fixed.
- The most relevant stuff in the preceding code lies within the ngOnInit() method; again, we made good use of the Angular zone to include the new externalProviderLogin function that we attached to the main window object instance--along with all the internal references--within the Angular context. This is the cornerstone of the workaround we used to manage the communication between the pop-up window and the main page. The pop-up window--when executing the <SCRIPT> tag received from the TokenController's ExternalLoginCallback response--will look up for that function within its window.opener, which will reference to the window object instance of our Angular app.