Oauth 2 popup with Angular 2 - javascript

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

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.

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

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);
}
}

I fiddled around with this for ages but in the end, the most robust way for me was to redirect the user to the oath page
window.location.href = '/auth/logintwitter';
do the oath dance in the backend (I used express) and then redirect back to a receiving front end page...
res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);
There are some idiosyncracies to my solution because e.g. I wanted to use only JsonWebToken on the client regardless of login type, but if you are interested, whole solution is here.
https://github.com/JavascriptMick/learntree.org

Related

Meteor Getting Prerender.io Working

I am trying to get prerender working on both local and prod. I feel like I have tried all implementations. I am still getting no static html in the body when using: ?_escaped_fragment_= at the end of the URL.
Here is my current Meteor implementation:
Meteor.startup(() => {
var prerenderio = Npm.require('prerender-node');
var token;
var serviceUrl;
var protocol;
var settings = Meteor.settings.PrerenderIO;
token = process.env.PRERENDERIO_TOKEN || (settings && settings.token);
protocol = process.env.PRERENDERIO_PROTOCOL || (settings && settings.protocol);
// service url (support `prerenderServiceUrl` (for historical reasons) and `serviceUrl`)
serviceUrl = settings && (settings.prerenderServiceUrl || settings.serviceUrl);
serviceUrl = process.env.PRERENDERIO_SERVICE_URL || serviceUrl;
if (token) {
if (serviceUrl) prerenderio.set('prerenderServiceUrl', serviceUrl);
prerenderio.set('prerenderToken', token);
if (protocol) prerenderio.set('protocol', protocol);
prerenderio.set('afterRender', function afterRender(error) {
if (error) {
console.log('prerenderio error', error); // eslint-disable-line no-console
return;
}
});
WebApp.rawConnectHandlers.use(prerenderio);
}
});
I have my settings file set up as so:
"PrerenderIO": {
"serviceUrl": "http://localhost:3033/",
"token": "mytoken"
},
Same for prod but without the serviceUrl. I did get the prerender server up and the page renders....but its still the default Meteor script rendered page. I also tried: <script> window.prerenderReady = false; </script> and then set it to true after my API content has loaded via our router (using ButterCMS for site content.
I have of course also added: <meta name="fragment" content="!"> to our sites head.
Prerender is still saying its not seen our token get used. I think I could be missing something obvious here....but not certain what it is.
That seems like the prerender middleware is not being run. Does Meteor leave the rawConnectHandlers in the order that they are added? Can you try this:
WebApp.rawConnectHandlers.use(function(req, res, next) {
console.log('before prerender:', req.url)
});
WebApp.rawConnectHandlers.use(prerenderio);
And see if you see any output in your logs for that showing what the incoming URL looks like. If you are accessing the ?_escaped_fragment_= URL, you should see get printed in that console.log statement.
Feel free to email us at support#prerender.io with a URL if you'd like us to help test.

Angular 5 + OAuth2: Token not getting set with libary [angular-oauth2-oidc]

I am trying to configure my Angular app to use the OAuth2 library (angular-oauth2-oidc).
In the file auth.service.ts my OAuthService is configured:
this.oauthService.loginUrl = 'https://serverdomain.com/authorization/';
this.oauthService.redirectUri = 'http://localhost:4200/auth';
this.oauthService.clientId = '1111-2222-3333-4444-5555-6666-7777';
this.oauthService.setStorage(localStorage);
this.oauthService.requireHttps = false;
this.oauthService.responseType = 'token';
this.oauthService.tokenEndpoint = 'https://serverdomain.com/token/';
this.oauthService.oidc = false;
this.oauthService.issuer = 'https://serverdomain.com/authorization/';
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.requestAccessToken = true;
this.oauthService.showDebugInformation = true;
this.oauthService.scope = 'openid profile email';
this.oauthService.tryLogin({
onTokenReceived: context => {
console.log(context);
}
});
obtainAccessToken() {
this.oauthService.initImplicitFlow();
}
isLoggedIn() {
if (this.oauthService.getAccessToken() === null) {
return false;
}
return true;
}
logout() {
this.oauthService.logOut();
location.reload();
}
logAuthData() {
console.log(this.oauthService.hasValidAccessToken());
}
In my home component I added a button to trigger the implicit flow and get an access token.
After initialization of the implicit flow the app redirects to the correct login page of the provider where I log in and get redirected to my redirectUri of my OAuth configuration.
BUT
If I try to get a state, for example I call isLoggedIn method, I get always false. Also, there is a false return at hasValidAccessToken().
Can anybody show how to correctly configure angular 5 and oauth2?
I need also a possibility to store my given access token to use them in my rest methods to get data.
Need to add JWKs token Validator in your configration. And set Jwks as per your Response type
this.oauthService.tokenValidationHandler = new JwksValidationHandler();

Why won't my Fluxible store dehydrate?

I have a pretty simple Fluxible store:
export default class NoteStore extends BaseStore {
static storeName = "NoteStore";
static handlers = {
[Actions.NEW_NOTES_FETCHED]: 'handleNewNotes'
};
constructor(dispatcher) {
super(dispatcher);
this.notes = [];
}
handleNewNotes(notes) {
this.notes = [];
var i;
for (i = 0; i < notes.length; i++){
this.notes.push(notes[i].content);
}
this.emitChange();
}
/* ... */
dehydrate() {
return { notes: this.notes };
}
rehydrate(state) {
this.notes = state.notes;
}
// Shouldn't be necessary to override?
shouldDehydrate() {
return true;
}
}
NEW_NOTES_FETCHED is dispatched by an action that gets data from my backend API, and the store listens for that event and pulls in the data from the payload. As far as I can tell, all this is working because everything runs perfectly when running in the client.
The problem I'm having is that the NoteStore doesn't seem to be getting dehydrated when the server calls app.dehydrate(). I look at the JSON embedded into the page and I don't see my store anywhere, though I do see information for the RouteStore.
I registerd my store with the FluxibleContext, but do I need to do something additionally to add it to the dehydrate chain?
App bootstrapping code if relevant:
const app = new Fluxible({ component: Root });
const AppRouteStore = RouteStore.withStaticRoutes(routes);
app.registerStore(AppRouteStore); // AppRouteStore appears in dehydrated JSON
app.registerStore(HtmlHeadStore); // Neither of these do, though HtmlHeadStore doesn't need to
app.registerStore(NoteStore);
export default app;
Ok, I figured out what was wrong. Basically, the action that was supposed to dispatch the NEW_NOTES_FETCHED event wasn't returning a promise and so the logic of handling the response from the backend server was never actually run, even though the request itself was made and I saw it appear on the backend's logs.
I was about to tear my hair out puzzling over this for so long, so hopefully someone can learn from my struggle!

firefox addon install.rdf pass data to server on update

Is there any chance to pass some data to my server through install.rdf when my Firefox add-on check server for update?
Example:
...
<em:updateURL>http://www.site.com/update.php?var=myData</em:updateURL>
...
where "myData" is saved in options.xul or in another place like simple-storage.
Yes, but it is quite nasty. The AddonManager will replace a bunch of predefined and dynamic properties in the URL:
Register a new component implementing nsIPropertyBag2 (or use an existing implementation, such as ["#mozilla.org/hash-property-bag;1"]).
Register your component in the nsICategoryManager under the "extension-update-params" category.
Since you mentioned simple-storage: restartless add-ons must also unregister their stuff when being unloaded.
There is a unit test demonstrating how this stuff works. You of course need to adapt it a bit (if alone for require("chrome").
I found one "simple solution" but I dont know if that is also good practice ...
var origLink = "http://www.site.net/update.php?var=myData";
var newsLink = "http://www.site.net/update.php?var=" + simplePref.prefs.myData;
const {Cc,Ci,Cu} = require("chrome");
var observer = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIObserver) || iid.equals(Ci.nsISupports)) return this;
},
observe: function(subject, topic, data){
if (topic == "http-on-modify-request"){
var channel = subject.QueryInterface(Ci.nsIChannel);
if (channel.originalURI.spec == origLink) {
channel.originalURI.spec = newsLink;
}
}
}
};
var ObsService = Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
ObsService.addObserver(observer, "http-on-modify-request", false);

How to make a cache/memorizer in BackboneModel

Let's suppose I have a View which can make model.fetch() and then a request to the server.
I would like to implement:
1) A checker able to memorise the result
2) refresh the result (making the request to the server) only if the last request to the server is older than ten minutes.
What should I do?
Is there already a piece of code to make that?
define([], function() {
var MyModel = Backbone.Model.extend({
url: function () {
return "http://localhost/restapi/model/";
},
fetch () {
if(diffTime > 10minutes) {
// make request to the server
}
else {
// return memo
}
}
});
});
You need to override the Backbone.sync method http://documentcloud.github.com/backbone/#Sync.
This code does the saving to local storage to implement a cache http://documentcloud.github.com/backbone/docs/backbone-localstorage.html.
It is fairly simple to add some logic in the "read" case to fetch from the server if the data is older than 10 minutes.
As codemonkey said, localstorage would be a good option. But if you don't want to use a library for that, you can use this class to extend those models who require the cache functionality.
var CachedModel = Backbone.Model.extend({
lastFetch: null, // millisec.
cache: { }
fetch: function () {
if(!this.lastFetch || (lastFetch - Date.now() > 10*60*1000) {
// make request to the server
}
else {
// return this.cache
}
}
});
I have found https://github.com/Ask11/backbone.offline to work really well for me.
The only vice is that it uses localStorage, you could also opt for more support by going with rewriting bits and pieces for use with amplify.store http://amplifyjs.com/api/store/.

Categories

Resources