Angular 2 integration for Oauth 2 popup authorization

I am in the process of updating an existing Angular application to utilize Angular 2. One challenge I am facing is opening an OAuth flow in a new pop-up window and then using window.postMessage to send a signal back to the Angular 2 app once the OAuth process is successfully completed.

The current setup in my Angular 2 service looks like this:

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 other components that user has logged in
          this._loggedInService.Stream.emit(null);
      }
    }

}

This is the template that is displayed 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 in this manner seems to disrupt the functionality of the Angular 2 app by losing reference to this.

So my question is whether it is appropriate to use window.addEventListener or if there is a better alternative than postMessage for communicating back to the Angular 2 app?

** I am relatively new to Angular 2 so any guidance would be greatly appreciated

Answer №1

If you're looking for a solid Angular2 OAuth2 skeleton application, I've got one available on Github that you might find helpful.

This application utilizes an Auth service for OAuth2 Implicit grants, which relies on a Window service to handle the popup window creation. It then keeps track of that window to retrieve the access token from the URL.

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

Below is a snippet from the login routine in the Auth service, providing insights into the process without diving into the entire project. I've included some extra comments to guide you through it.

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

    this.intervalId = setInterval(() => {
        if (loopCount-- < 0) { 
            clearInterval(this.intervalId);
            this.emitAuthStatus(false);
            this.windowHandle.close();
        } else { 
            var href:string;
            try {
                href = this.windowHandle.location.href;
            } catch (e) {
              
            }
            if (href != null) { 
                var re = /access_token=(.*)/;
                var found = href.match(re);
                if (found) { 
                    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);
}

If you encounter any issues or need assistance setting up the application, feel free to reach out with your questions.

Answer №2

After some investigation, I discovered the root of the problem. It turned out that I was de-referencing this. Consulting this informative GitHub wiki page helped clarify things for me.

To resolve the issue in my specific case, I needed to take a few steps. First, I created a service that handled the addition of an event listener:

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

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

By utilizing the addPostMessageListener method from this service, I could implement a function in another service to trigger accordingly:

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

}

Subsequently, everything functioned as intended by preserving the reference to this.

Answer №3

In my opinion, this approach is commonly used in Angular 2:

(This code snippet is written in Dart but TypeScript should have a similar syntax)

@Injectable()
class SomeService {
  DomAdapter dom;
  SomeService(this.dom) {
    dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
  }
}

Answer №4

After experimenting with different approaches, I found that the most reliable method for me was to direct the user to the authentication page.

window.location.href = '/auth/logintwitter';

I handled the authentication process in the backend (using express) and then redirected back to a designated front end page.

res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);

My solution has some unique aspects, such as using only JsonWebToken on the client side regardless of login type. If you're curious, you can view the entire solution here.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error compiling SCSS in Angular 6 due to a util.js issue

As a novice in the world of Angular 6, I am currently exploring the Angular CLI and trying to grasp the file structure. My goal is to utilize SCSS for creating a unified global stylesheet. However, during compilation, an error keeps popping up: ERROR in . ...

Is there a way to receive notifications on an Android device when the real-time data updates through Firebase Cloud Messaging (FC

I am attempting to implement push notifications in an Android device using Firebase Realtime Database. For example, if an installed app is killed or running in the background, and a user posts a message in a group (resulting in a new child being added in t ...

What is the process for setting up a subrouter using React Router v6?

This is the current React Router setup I am using: const router = createBrowserRouter([ { path: "/", element: ( <Page activeNav="home" > <Home /> </Page> ) }, { ...

Executing asynchronous JavaScript calls within a loop

I've encountered an issue with asynchronous calls in JavaScript where the function is receiving unexpected values. Take a look at the following pseudo code: i=0; while(i<10){ var obj= {something, i}; getcontent(obj); / ...

PHP - Unable to verify session during script execution

I'm currently working on a PHP script with a lengthy execution time, and I am looking for a way to update the client on the progress of the script. Since the script is invoked via AJAX, output buffering is not a feasible option (and I prefer to keep ...

Tips for showing various tooltip text when iterating through a list?

I am currently working on a project where I am looping through a list and attempting to assign different tooltip text to various icons. However, I am struggling with the implementation. Here is a snippet of my code: <React.Fragment key={sv.key ...

Utilize a Material UI GridList to transform images into a captivating background display

Incorporating a GridList displaying a variety of images fetched from an external source, I have successfully created an ever-changing image gallery. Now, my goal is to convert this dynamic image gallery into grayscale or transparency and utilize it as a ba ...

In the realm of Laravel, Vue, and Javascript, one may question: what is the best approach to omitting a key

When working with JSON data, I encountered a problem where leaving some keys unfilled resulted in incorrect results. I want to find a way to skip these keys if they are not filled. I have shared my code for both the backend and frontend below. Backend La ...

Disguising the Navigation Bar when choosing from various databases

I am currently facing the following issue: <div class="container"> <h3 class="d-flex justify-content-center">Database</h3> <div class="row"> <div class="col-xs-12"> < ...

Tips on conducting a statistical analysis without having to wait for the completion of an AJAX response

I am looking to track the number of clicks on a specific div with the id #counter and then redirect the user to a different website. To achieve this, I have implemented an ajax call on the click event of the #counter div. Upon successful completion of the ...

Using jQuery to create dynamic elements that fade out with timers

My website has a simple message system that displays messages in a floating div at the top of the page. Each message is supposed to fade out after a certain amount of time, but I want users to be able to pause the fading process by hovering over the messag ...

Using backslashes to escape JSON values within a value in Angular

When retrieving JSON data from the backend, I often encounter an issue where the value is set to "key": "\$hello" and it results in an "Unexpected token d". Is there a way in Angular to handle or escape these characters once received from the server? ...

Ways to invoke a controller function from a window listener function

Is there a way to trigger the close function from window.onbeforeunload even when closing the app through 'right click' -> 'close window'? It seems that this.close() is not working in this scenario, possibly due to scope issues. The ...

Explore visuals in the component repository

I am currently working on an Angular 7 component library and am facing some challenges in adding images for buttons and other elements. My project structure is organized as follows: projects components src lib myComponent assets ...

Declaring scoped runtime interfaces with Typescript

I need to create a global interface that can be accessed at runtime under a specific name. /** Here is my code that will be injected */ // import Vue from "vue"; <- having two vue instances may cause issues // ts-ignore <- Vue is only ava ...

Having trouble locating the module for my custom TypeScript module

Exciting news! I have recently released a new TypeScript-based module on the NPM registry, called ooafs. However, when attempting to install and import it into another TypeScript project, an error pops up in VSCode stating: Cannot find module 'ooafs&a ...

Using JavaScript to pre-select a radio button without any user interaction

Is there a way to programmatically set a radio button in a group without physically clicking on the button? I am attempting to open a jQuery page and depending on a stored value, the corresponding radio button should be selected. I have researched similar ...

Ways to make the input field appear invalid without the use of forms, causing the bottom outline to turn red when a specific condition is met

Currently, the application does not utilize any forms. I am interested in making the input field invalid under certain conditions within the component. For example, if a user leaves the input field blank, I would like the bottom outline to turn red when an ...

Redirecting with React Router outside of a route component

I am using React Router in my application to manage the routing functionalities. However, I have encountered an issue where I need to redirect the user from the Header component, which is not nested inside the Router component. As a result, I am unable t ...

The Material UI button shifts to a different row

I need help adjusting the spacing between text and a button on my webpage. Currently, they are too close to each other with no space in between. How can I add some space without causing the button to move to the next line? const useStyles = makeStyles((the ...