Using: ember-cli v2.5, ember-simple-auth v1.1.0
I got trouble understanding if I can properly store additional token inside a custom ember-simple-auth's cookie based session-store.
I'm trying to store a shopping-cart token to be sure it survives browser refresh.
I started to create a shopping-cart service to handle init, add, remove etc ... regarding if the session has a token.
Here is my app/session-stores/application.js
// app/session-stores/application.js
import Cookie from 'ember-simple-auth/session-stores/cookie';
export default Cookie.extend({
orderToken: null
});
Doesn't seems to be used. The session service still use the adaptive store.
Here is my shopping-cart service
// app/services/shopping-cart.js
import Ember from 'ember';
export default Ember.Service.extend({
store: Ember.inject.service('store'),
session: Ember.inject.service('session'),
basket: null,
[...]
init() {
this._super(...arguments);
let orderToken = this.get('session.orderToken'); // First try
// let orderToken = this.get('session.store.orderToken'); // Second Try
if (orderToken) {
this.get('store').findRecord('order', orderToken).then((order) => {
this.set('basket', order);
})
}
},
[...]
_createBasket() {
return this.get('store').createRecord('order', {}).save().then((order) => {
this.set('basket', order);
this.set('session.orderToken', order.token); // First try
// this.set('session.store.orderToken', order.token); // Second Try
});
}
})
Then the idea will be to inject the service wherever I need. Unfortunately, It doesn't work, and I don't really know if I can do it or if it's the right way to do it.
Any advices, answers will be much appreciate !
I am currently using ember-cookie and it's working like a charm. I am juste trying to play with ember-simple-auth and understand all my possibilities.
Related
I have a basic expo app with React Navigation.
In the top function Navigation I am initiating a useMutation call to an Apollo server like so:
import { callToServer, useMutation } from '../graphQL';
function Navigation() {
console.log("RENDERED");
const [call] = useMutation(callToServer);
call({ variables: { uid: 'xyz', phoneNumber: '123' } });
...
And my GraphQL settings is as follows:
import {
ApolloClient,
createHttpLink,
InMemoryCache,
useMutation,
} from '#apollo/client';
import { onError } from '#apollo/client/link/error';
import { callToServer } from './authAPI';
const cache = new InMemoryCache();
const httpLink = createHttpLink({
uri: `XXXXXXX/my-app/us-central1/graphql`,
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
});
const client = new ApolloClient({
cache,
link: errorLink.concat(httpLink),
});
export {
useMutation,
callToServer,
};
export default client;
I want to clarify that I removed the httpLink from the client setting and I still get the two renders per call. I can see in the console that console.log("RENDERED") prints three times. Once when the app loads (normal) and twice after the useMutation call (not normal?)
What's going on here? Why is react re-renders twice per useMutation call? How do I avoid it?
UPDATE
I did further digging and it seems that useMutation does indeed cause the App to render twice - once when the request is sent, and once when it receives a response. I'm not sure I'm loving this default behavior which seems to have no way to disable. Why not let us decide if we want to re-render the App?
If someone has more insight to offer, Id love to hear about it.
Probably it's too late and maybe you've already found the solution, but still...
As I see you do not need data returned from mutation in the code above. In this case you can use useMutation option "ignoreResults" and set it to "true". So mutation will not update "data" property and will not cause any render.
As I understand from the documentation a Service is basically a singleton object used to provide services to other objects through the application lifecycle. I have a user management service which I use to save an authentication token after the user logs in using the route /users/login. But transitioning to another route (/composer for instance) causes the service instance to be recreated and hence it loses all the stored data. Doesn't this contradict the fact that it should live as long as the application does or do I have a wrong of idea this whole lifecycle thingy?
I'm injecting the service in all my routes as below:
authenticationService: Ember.inject.service('authentication-service'),
The service itself is only a set of getters and setters:
import Ember from 'ember';
export default Ember.Service.extend({
currentUser: undefined,
jwtToken: undefined,
// ================================================================================================================ \\
// ================================================================================================================ \\
// ================================================================================================================ \\
setCurrentUser(user) {
this.currentUser = user ;
},
getCurrentUser() {
return this.currentUser ;
},
isLoggedIn() {
return Boolean(this.currentUser) ;
},
getJwtToken() {
return this.jwtToken ? this.jwtToken : '' ;
},
setJwtToken(jwtToken) {
this.jwtToken = jwtToken ;
}
});
Here is how the login token is handled:
actions: {
onSubmitLoginForm() {
if (!this.validateLoginForm()) {
return ;
}
var self = this ;
Ember.$.post('login/', {
'username': this.controller.get('username'),
'password': this.controller.get('password'),
'email': this.controller.get('email'),
}, function(data) {
console.log(data) ;
if (data['success'] === 'Ok') {
self.get('authenticationService').setJwtToken(data['auth']['jwt']) ;
var user = self.get('store').createRecord('user', {
username: data['auth']['user']['username'],
email : data['auth']['user']['email'],
mongoId : data['auth']['user']['id']['$oid'],
}) ;
self.get('authenticationService').setCurrentUser(user) ;
self.transitionTo('home') ;
console.log('logged in') ;
console.log(self.get('authenticationService').getJwtToken()) ;
console.log(self.get('authenticationService').getCurrentUser()) ;
console.log(self.get('authenticationService').isLoggedIn()) ;
} else {
self.transitionTo('error') ;
}
}) ;
},
}
I'm not looking for suggestions on using some other means of persistence such as IndexedDB; I'm willing to understand how this thing actually works so any explanation is appreciated.
Yes, you understand it right - service is a singletone and I can assure you that service persists its state between trasitions. But to make a transition, you must use link-to helper. If you are changing url manually, you reloading your app instead of transitioning. And app reload of course causes state reset. You should use any available kind of storage to persist state between page reloads. It may be local storage, session storage, cookies etc.
Also, in Ember we don't use such code: this.currentUser = user ; on Ember objects. We use this.set('currentUser', user); instead. Otherwise Ember would not be able to rerender templates, update computed properties and work properly.
And finally, you shouldn't build auth solution from zero. It's very hard and complex thing to do. Instead, you can use ember-simple-auth addon and build authentication process on top of it. It will be much easier and result will be more reliable.
We're working with two ember applications that each run different version of ember and ember-simple-auth, and want to get ember-simple-auth to work well with both version.
The old app
Ember 1.8.1
Ember-simple-auth 0.7.3
The new app
Ember 2.3.1
Ember-simple-auth 1.0.1
Uses cookie session store
We trying to change the session API for the older version so that it stores the access and refresh tokens correctly so the new app can use it.
So far, we’ve tried overriding the setup and updateStore methods to work with the authenticated nested object but are still running into issues.
Disclaimer - Patrick Berkeley and I work together. We found a solution after posting this question that I figured I would share.
In order for a 0.7.3 version of ember-simple-auth's cookie store to play nicely with a 1.0.0 version, we did have to normalize how the cookie was being formatted on the app with the earlier version in a few key places, mostly centered around the session object (the 0.7.3 session is an ObjectProxy that can be extended in the consuming app to create your own custom session).
The methods that we needed to override, centered around the structure of data being passed to the cookie store to persist and what was being returned when a session was being restored. The key difference is on version 0.7.3, the access_token, etc is stored top-level on the content object property of the session. With 1.0.0. this is nested inside another object inside content with the property name of authenticated. We therefore needed to ensure that everywhere we were making the assumption to set or get the access_token at the top level, we should instead retrieve one level deeper. With that in mind, we came up with these methods being overridden in our custom session object:
// alias access_token to point to new place
access_token: Ember.computed.alias('content.authenticated.access_token'),
// overridden methods to handle v2 cookie structure
restore: function() {
return new Ember.RSVP.Promise((resolve, reject) => {
const restoredContent = this.store.restore();
const authenticator = restoredContent.authenticated.authenticator;
if (!!authenticator) {
delete restoredContent.authenticated.authenticator;
this.container.lookup(authenticator).restore(restoredContent.authenticated).then(function(content) {
this.setup(authenticator, content);
resolve();
}, () => {
this.store.clear();
reject();
});
} else {
this.store.clear();
reject();
}
});
},
updateStore: function() {
let data = this.content;
if (!Ember.isEmpty(this.authenticator)) {
Ember.set(data, 'authenticated', Ember.merge({ authenticator: this.authenticator }, data.authenticated || {}));
}
if (!Ember.isEmpty(data)) {
this.store.persist(data);
}
},
setup(authenticator, authenticatedContent, trigger) {
trigger = !!trigger && !this.get('isAuthenticated');
this.beginPropertyChanges();
this.setProperties({
isAuthenticated: true,
authenticator
});
Ember.set(this, 'content.authenticated', authenticatedContent);
this.bindToAuthenticatorEvents();
this.updateStore();
this.endPropertyChanges();
if (trigger) {
this.trigger('sessionAuthenticationSucceeded');
}
},
clear: function(trigger) {
trigger = !!trigger && this.get('isAuthenticated');
this.beginPropertyChanges();
this.setProperties({
isAuthenticated: false,
authenticator: null
});
Ember.set(this.content, 'authenticated', {});
this.store.clear();
this.endPropertyChanges();
if (trigger) {
this.trigger('sessionInvalidationSucceeded');
}
},
bindToStoreEvents: function() {
this.store.on('sessionDataUpdated', (content) => {
const authenticator = content.authenticated.authenticator;
this.set('content', content);
if (!!authenticator) {
delete content.authenticated.authenticator;
this.container.lookup(authenticator).restore(content.authenticated).then((content) => {
this.setup(authenticator, content, true);
}, () => {
this.clear(true);
});
} else {
this.clear(true);
}
});
}.observes('store'),
This took us most of the way there. We just needed to ensure that the authenticator name that we use matches the name on 1.0.0. Instead of 'simple-auth-authenticator:oauth2-password-grant', we needed to rename our authenticator via an initializer to 'authenticator:oauth2'. This ensures that the apps with the newer version will be able to handle the correct authenticator events when the cookie session data changes. The initializer logic is simple enough:
import OAuth2 from 'simple-auth-oauth2/authenticators/oauth2';
export default {
name: 'oauth2',
before: 'simple-auth',
initialize: function(container) {
container.register('authenticator:oauth2', OAuth2);
}
};
The above satisfies our needs- we can sign in to an app using ember-simple-auth 0.7.3 and have the cookie session stored and formatted properly to be handled by another app on ember-simple-auth 1.0.0.
Ideally, we would just update the Ember and Ember Simple Auth versions of the app though business needs and the fact that we wanted to focus our energies on the v2 versions (which are completely fresh and new code bases) propelled us to go down this path.
I have a basic working authentication system in my Ember app. I can receive a JWT and my app will log me in. The problem is that I can't access anything with something like this.get('session').get('data.id') as shown in an example on ember-simple-auth's GitHub page.
Here's the response from my authentication request:
{token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFiY…yMyJ9.X0O3xMVikn-5l9gXBU5a2XF6vlMmTzm4mCqUNA68e-A", test: "abc123"}
Here's the payload of the token:
{
"id": "abc123"
}
Yet, calling this.get('session').get('data.id') doesn't return anything. I also tried other things like this.get('session').get('id').
this.get('session').get('data') returns:
{"authenticated":{"authenticator":"authenticator:jwt","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFiYzEyMyJ9.X0O3xMVikn-5l9gXBU5a2XF6vlMmTzm4mCqUNA68e-A","test":"abc123"}}
So there is technically a way to read test but it doesn't seem like the right way.
this.get('session') exists, but is empty. Setting properties work well and they can be accessed afterwards.
How do I access the claims? ember-simple-auth-token has a specific authenticator for JWT so I assume it should be able to read the token.
You can get the decoded payload like this:
JSON.parse(atob(this.get('session.data.authenticated.token').split('.')[1]))
Which is talked about in this issue.
UPDATE
Just looked at this again and realised that I might have misunderstood you.
The method I mentioned above is how to decode the claims from the token, however that's not how you would access the claims from templates/routes etc.
Here is a good blog post that shows how to make the claims more easily accessible.
Basically whenever a user is authenticated, a property is added to the session which allows the claims to be accessed like this.get('session.claims.id').
In the blog post a user is fetched from the api and saved as session.account. If you would rather just set the claims directly from the token to the session you can do this:
services/session-account.js
import Ember from 'ember';
const { inject: { service }, RSVP } = Ember;
export default Ember.Service.extend({
session: service('session'),
store: service(),
loadCurrentUser() {
const token = this.get('session.data.authenticated.token');
if (!Ember.isEmpty(token)) {
var claims = JSON.parse(atob(token.split('.')[1]));
this.set('session.claims', claims);
}
}
});
Hopefully that's more what you were looking for.
Alright, this is my first question on SO so I'll try to make it a good one sorry ahead of time.
I've been using ember-cli to work on a product. I am using Firebase simple login for authentication and have created an initializer that adds an auth object to all my controllers and routes.
This works, but... There seems to be a delay for the redirect checking. example I go to /secret and this should redirect me back to the /login route and it does but there is a slight delay where I can see the template
I've tried to create a gist with all the necessary information. Let me know if there is anything else I can provide to help out
https://gist.github.com/mbertino/060e96e532f8ce05d2d0
You could provide a flag on your auth object, which you use in your route to conditionally display the content:
import Ember from 'ember';
var firebase = new window.Firebase( window.MyAppENV.APP.firebaseURL );
export default Ember.Object.extend({
authed: false,
init: function(route) {
var self = this;
self.authClient = new window.FirebaseSimpleLogin( firebase ), function(error, user) {
self.set( 'authed', false );
if (error) {
alert('Authentication failed: ' + error);
} else if (user) {
self.set( 'authed', true );
// if already a user redirect to the secret resource
route.transitionTo('secret')
} else if (route.routeName !== 'login'){
// if the route is anything but the login route and there is not
// a user then redirect to the login resource
route.transitionTo('login')
}
}.bind(this));
You then can use {{#if auth.authed}} in your templates to reveal private data, or display different content.
Instead of setting the flag above, I use a watcher on the authenticated status, this is provided by the API (documented here: https://www.firebase.com/docs/web/guide/user-auth.html#section-monitoring-authentication):
// also in init()
self.authRef = new window.Firebase( firebase + '/.info/authenticated' );
self.authRef.on( 'value', function( snap ) {
var oldAuthed = self.get('authed');
if ( snap.val() === true ) {
self.set( 'authed', true );
if ( ! oldAuthed ) {
// Status switched form unauthenticated to authenticated
// go to your route here
);
}
} else {
self.set( 'authed', false );
if ( oldAuthed ) {
// User was logged in, is now logged out
}
}
});
Keep in mind that it's JavaScript, and nothing you can do will be fool-proof. In other words, you can try to hide anything, but someone who really wants to can always hack into your data structures and reveal a hidden page that way. If you don't want people to see something, make sure they can't read it in the first place (by locking down access through the Firebase privilege system).