Oauth 2 popup with Angular 2

11,115

Solution 1

So with a bit of investigation found out the problem. I was de-referencing this. This github wiki helped me understand it a bit more.

To solve it for my case needed to do a couple of things. Firstly I created a service that encapsulated the adding of an eventListener

import {BrowserDomAdapter} from 'angular2/platform/browser';

export class PostMessageService {
   dom = new BrowserDomAdapter();
   addPostMessageListener(fn: EventListener): void {
     this.dom.getGlobalEventTarget('window').addEventListener('message', fn,false)
   }
}

Then using this addPostMessageListener I can attach a function in my other service to fire

constructor(public _postMessageService: PostMessageService,
    public _router: Router) {
    // Set up a Post Message Listener
    this._postMessageService.addPostMessageListener((event) => 
          this.onPostMessage(event)); // This is the important as it means I keep the reference to this

}

Then it works how I expected keeping the reference to this

Solution 2

I have a complete Angular2 OAuth2 skeleton application on Github that you can refer to.

It makes use of an Auth service for OAuth2 Implicit grants that in turn uses a Window service to create the popup window. It then monitors that window for the access token on the URL.

You can access the demo OAuth2 Angular code (with Webpack) here.

Here is the login routine from the Auth service, which will give you an idea of what's going on without having to look at the entire project. I've added a few extra comments in there for you.

public doLogin() {
    var loopCount = this.loopCount;
    this.windowHandle = this.windows.createWindow(this.oAuthTokenUrl, 'OAuth2 Login');

    this.intervalId = setInterval(() => {
        if (loopCount-- < 0) { // if we get below 0, it's a timeout and we close the window
            clearInterval(this.intervalId);
            this.emitAuthStatus(false);
            this.windowHandle.close();
        } else { // otherwise we check the URL of the window
            var href:string;
            try {
                href = this.windowHandle.location.href;
            } catch (e) {
                //console.log('Error:', e);
            }
            if (href != null) { // if the URL is not null
                var re = /access_token=(.*)/;
                var found = href.match(re);
                if (found) { // and if the URL has an access token then process the URL for access token and expiration time
                    console.log("Callback URL:", href);
                    clearInterval(this.intervalId);
                    var parsed = this.parse(href.substr(this.oAuthCallbackUrl.length + 1));
                    var expiresSeconds = Number(parsed.expires_in) || 1800;

                    this.token = parsed.access_token;
                    if (this.token) {
                        this.authenticated = true;
                    }

                    this.startExpiresTimer(expiresSeconds);
                    this.expires = new Date();
                    this.expires = this.expires.setSeconds(this.expires.getSeconds() + expiresSeconds);

                    this.windowHandle.close();
                    this.emitAuthStatus(true);
                    this.fetchUserInfo();
                }
            }
        }
    }, this.intervalLength);
}

Feel free to ask if you have any questions or problems getting the app up and running.

Solution 3

I think this is the Angular2 way:

(Dart code but TS should be quite similar)

@Injectable()
class SomeService {
  DomAdapter dom;
  SomeService(this.dom) {
    dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
  }
}
Share:
11,115
royka
Author by

royka

Computer Scientist and an android convert. Interested in a lot of things to do with computers

Updated on June 24, 2022

Comments

  • royka
    royka almost 2 years

    I'm upgrading/rewriting an existing angular app to use angular2. My problem is that I want to open a OAuth flow in a new pop up window and once the OAuth flow is completed use window.postMessage to communicate back to the angular 2 app that the OAuth flow was successful.

    Currently what I have is in the angular 2 service is

    export class ApiService { 
        constructor(private _loggedInService: LoggedInService) {
            window.addEventListener('message', this.onPostMessage, false);
         }
    
        startOAuthFlow() {
           var options = 'left=100,top=10,width=400,height=500';
           window.open('http://site/connect-auth', , options);
        }
    
        onPostMessage(event) {
          if(event.data.status === "200") {
              // Use an EventEmitter to notify the other components that user logged in
              this._loggedInService.Stream.emit(null);
          }
        }
    
    }
    

    This template that is loaded at the end of the OAuth flow

    <html>
      <head>
        <title>OAuth callback</title>
        <script>
          var POST_ORIGIN_URI = 'localhost:8000';
          var message = {"status": "200", "jwt":"2"};
          window.opener.postMessage(message, POST_ORIGIN_URI);
          window.close();
        </script>
      </head>
    </html>
    

    Using window.addEventListener like this seems to completely break the angular 2 app, dereferencing this.

    So my question is can I use window.addEventListener or should I not use postMessage to communicate back to the angular2 app?

    ** Complete angular2 noob so any help is appreciated