I'm using the ember-cli-simple-auth addon with Torii for my authentication flow.
So far I've managed to get authentication working using both a custom Torii Provider combined with a custom Simple Auth Authenticator.
I'd now like to use a custom Simple Auth Authorizer to inject the access token into requests.
Following the documentation https://github.com/simplabs/ember-simple-auth#implementing-a-custom-authorizer I've added a custom authorizer & initializer
authorizers/myservice.js
import Base from 'simple-auth/authorizers/base';
import Ember from 'ember';
export default Base.extend({
/**
#method authorize
#param {jqXHR} jqXHR The XHR request to authorize (see http://api.jquery.com/jQuery.ajax/#jqXHR)
#param {Object} requestOptions The options as provided to the `$.ajax` method (see http://api.jquery.com/jQuery.ajaxPrefilter/)
*/
authorize: function(jqXHR) {
var accessToken = this.get('session.content.token');
if (this.get('session.isAuthenticated') && !Ember.isEmpty(accessToken)) {
jqXHR.setRequestHeader('Authorization', 'Bearer ' + accessToken);
}
}
});
initializers/authorization.js
import MyserviceAuthorizer from '../authorizers/myservice';
export function initialize(container, application) {
container.register('authorizer:myservice', MyserviceAuthorizer);
}
export default {
name: 'authorization',
before: 'simple-auth',
initialize: initialize
};
& included in config/environment.jsin the development environment
ENV['simple-auth'] = {
authorizer: 'authorizer:myservice',
crossOriginWhitelist: ['*']
}
Unfortunately by adding this it has now broken the authentication.
It looks like Torii is no longer receiving the response.
The response from the provider is missing these required response params: access_token, token_type, expires_in
I've included both the Torii Provider code & Simple Auth Authenticator code here too.
Any suggestions or help would be very much appreciated, i'm a bit stuck with this.
torii-providers/myservice.js
import Provider from 'torii/providers/oauth2-bearer';
import {configurable} from 'torii/configuration';
import env from '../config/environment';
export default Provider.extend({
name: 'myservice',
baseUrl: (env.api_host + '/oauth/authorize'),
responseParams: ['access_token', 'token_type', 'expires_in'],
redirectUri: configurable('redirectUri', function(){
// A hack that allows redirectUri to be configurable
// but default to the superclass
return this._super();
})
});
And a custom Simple Auth authenticator
authenticators/myservice.js
import Ember from 'ember';
import Base from 'simple-auth/authenticators/base';
import ajax from 'ic-ajax';
export default Base.extend({
restore: function(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
if(!Ember.isEmpty(data.currentUser)) {
resolve(data);
} else {
reject();
}
});
},
authenticate: function(options) {
return this.fetchOauthData(options).then(this.fetchUserData.bind(this));
},
fetchUserData: function(oauthData) {
var token = oauthData.token.access_token;
return ajax({
url: '/api/v1/users/me',
type: "GET",
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + token);
}
}).then(function(userJSON){
return {
currentUser: userJSON.user,
token: token
};
});
},
fetchOauthData: function(options) {
return new Ember.RSVP.Promise(function(resolve, reject) {
options.torii.open(options.provider).then(function(oauthData) {
resolve({
provider: oauthData.provider,
token: oauthData.authorizationToken
});
}, function(error) {
reject(error);
});
});
}
});
This might be related to the fact, that Ember CLI automatically registers everything under app folder in the container. Although the following quote from Ember CLI documentation doesn't explain that clearly, but it gives a hint:
All modules in the app folder can be loaded by the resolver but typically classes such as mixins and utils should be loaded manually with an import statement.
If your authorizer file is app/authorizers/myservice.js, Ember CLI will register it under 'authorizer:myservice' name on a container. Container in turn will create a singleton instance when it is looked up. Since you do the same registration in the initializer, there may be some kind of a conflict.
Related
I want to send a POST request to an external API with axios in a nuxt projekt where I use the nuxt auth module.
When a user is authenticated axios seems to automatically add an authorization header (which is fine and often required for calls to my backend API). However, when doing calls to an external API the header might not be accepted and cause the call to fail.
Is there any way to specify for which URLs the auth header should be added or excluded?
Here are the configurations of the auth and axios module in my nuxt.config
// Axios module configuration
axios: {
baseURL: '//localhost:5000',
},
// Auth module configuration
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'token' },
logout: { url: '/auth/logout', method: 'delete' },
user: { url: '/auth/user', method: 'get', propertyName: 'user' },
},
},
},
}
Some more background:
In my particular usecase I want to upload a file to an Amazon S3 bucket, so I create a presigned upload request and then want to upload the file directly into the bucket. This works perfectly fine as long as the user is not authenticated.
const { data } = await this.$axios.get('/store/upload-request', {
params: { type: imageFile.type },
})
const { url, fields } = data
const formData = new FormData()
for (const [field, value] of Object.entries(fields)) {
formData.append(field, value)
}
formData.append('file', imageFile)
await this.$axios.post(url, formData)
I tried to unset the Auth header via the request config:
const config = {
transformRequest: (data, headers) => {
delete headers.common.Authorization
}
}
await this.$axios.post(url, formData, config)
This seems to prevent all formData related headers to be added. Also setting any header in the config via the headers property or in the transformRequest function does not work, which again causes the call to the external API to fail obviously (The request will be sent without any of these specific headers).
As I'm working with the nuxt axios module I'm not sure how to add an interceptor to the axios instance as described here or here.
Any help or hints on where to find help is very much appreciated :)
Try the following
Solution 1, create a new axios instance in your plugins folder:
export default function ({ $axios }, inject) {
// Create a custom axios instance
const api = $axios.create({
headers: {
// headers you need
}
})
// Inject to context as $api
inject('api', api)
}
Declare this plugin in nuxt.config.js, then you can send your request :
this.$api.$put(...)
Solution 2, declare axios as a plugin in plugins/axios.js and set the hearders according to the request url:
export default function({ $axios, redirect, app }) {
const apiS3BaseUrl = // Your s3 base url here
$axios.onRequest(config => {
if (config.url.includes(apiS3BaseUrl) {
setToken(false)
// Or delete $axios.defaults.headers.common['Authorization']
} else {
// Your current axios config here
}
});
}
Declare this plugin in nuxt.config.js
Personally I use the first solution, it doesn't matter if someday the s3 url changes.
Here is the doc
You can pass the below configuration to nuxt-auth. Beware, those plugins are not related to the root configuration, but related to the nuxt-auth package.
nuxt.config.js
auth: {
redirect: {
login: '/login',
home: '/',
logout: '/login',
callback: false,
},
strategies: {
...
},
plugins: ['~/plugins/config-file-for-nuxt-auth.js'],
},
Then, create a plugin file that will serve as configuration for #nuxt/auth (you need to have #nuxt/axios installed of course.
PS: in this file, exampleBaseUrlForAxios is used as an example to set the variable for the axios calls while using #nuxt/auth.
config-file-for-nuxt-auth.js
export default ({ $axios, $config: { exampleBaseUrlForAxios } }) => {
$axios.defaults.baseURL = exampleBaseUrlForAxios
// I guess that any usual axios configuration can be done here
}
This is the recommended way of doing things as explained in this article. Basically, you can pass runtime variables to your project when you're using this. Hence, here we are passing a EXAMPLE_BASE_URL_FOR_AXIOS variable (located in .env) and renaming it to a name that we wish to use in our project.
nuxt.config.js
export default {
publicRuntimeConfig: {
exampleBaseUrlForAxios: process.env.EXAMPLE_BASE_URL_FOR_AXIOS,
}
}
I'm new to Vue.js Nuxt and all front-end stuff.
I have a question about API calls. I'm not sure what is the right way, the best practice here.
I have a store. In that store, I have actions that are calling my API and sets state eg.
async fetchArticle({ state, commit }, uuid) {
const response = await this.$axios.get(`articles/${uuid}/`)
commit('SET_ARTICLE', response.data)
},
And that is fine it is working for one component.
But what if I want to just fetch the article and not changing the state.
To be DRY first thing that comes to my mind is to create the service layer that is fetching the data and is used where it is needed.
Is it the right approach? Where can I find some real-world examples that I can take inspiration from?
Using the repository pattern to abstract your API is definitely a good idea! Whether you use the #nuxtjs/axios module or the #nuxt/http module, you can pass either instance to your repository class/function. Below a real world example of an abstracted "repository.js" file.
export default $axios => resource => ({
index() {
return $axios.$get(`/${resource}`)
},
create(payload) {
return $axios.$post(`/${resource}`, payload)
},
show(id) {
return $axios.$get(`/${resource}/${id}`)
},
update(payload, id) {
return $axios.$put(`/${resource}/${id}`, payload)
},
delete(id) {
return $axios.$delete(`/${resource}/${id}`)
}
})
You can then create a plugin to initialize all different kinds of repositories for your endpoints:
import createRepository from '~/path/to/repository.js'
export default (ctx, inject) => {
const repositoryWithAxios = createRepository(ctx.$axios)
const repositories = {
posts: repositoryWithAxios('posts'),
users: repositoryWithAxios('users')
//...
}
inject('repositories', repositories)
}
Further read: Organize and decouple your API calls in Nuxt.js
I will an example of a service layer implementation for my portfolio to create my dashboard that shows some statics about my github and stackoverflow profiles, to do this i created a folder called services inside the project root :
pages
services
|_AxiosConfig.js
|_GitHubService.js
|_StackoverflowService.js
...
in the AxiosConfig.js file i put i created an axios instance with its configuration :
import axios from 'axios';
const clientAPI = url =>
axios.create({
baseURL: url,
withCredentials: false,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
export default clientAPI;
then in my GitHubService.js i imported that axios instance called clientAPI which i used to my requests :
import clientAPI from './AxiosConfig';
const baseURL = 'https://api.github.com';
export default {
getUser(name) {
return clientAPI(baseURL).get('/users/' + name);
},
getRepos(name){
return clientAPI(baseURL).get('/users/' + name+'/repos');
},
getEvents(name,page){
return clientAPI(baseURL).get('/users/' + name+'/events?per_page=100&page='+page);
},
getLastYearCommits(name,repo){
return clientAPI(baseURL).get('/repos/' + name+'/'+repo+'/stats/commit_activity');
}
};
then in my page i used asyncData hook to fetch my data :
import GitHubService from '../../services/GitHubService'
export default {
...
async asyncData({ error }) {
try {
const { data } = await GitHubService.getUser("boussadjra");
const resRepos = await GitHubService.getRepos("boussadjra");
return {
user: data,
repos: resRepos.data
};
} catch (e) {
error({
statusCode: 503,
message: "We cannot find the user"
});
}
}
I wanted to use axios in my service/service.js file, so instead of passing axios, I accessed it directly like this:
export default {
async fetchArticle() {
let response = await $nuxt.$axios.$get('/api-url')
return response
},
}
In Nuxt, if you want to just get the data without keeping it in your store, you could use the asyncData function, which asynchronously loads data (from API calls and the like) and pushes it into the component's data object before rendering.
I tried to setup a facebook login using Torii and Simple-Auth Torii authenticator. I am using ember-cli.
Here is my configuration :
// config/environment.js
ENV['torii'] = {
providers: {
'facebook-oauth2': {
apiKey: 'my facebook app id'
}
}
}
Here is my login code :
// app/controllers/login.js
import Ember from 'ember';
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(LoginControllerMixin, {
actions: {
authenticateWithTorii: function(){
this.get('session').authenticate(
'simple-auth-authenticator:torii',
'facebook-oauth2'
).then(
function(data) {
alert('SUCCESS ' + data);
},
function(error) {
alert('There was an error when trying to sign you in: ' + error);
}
);
}
}
});
The popup is opening asking for authorization, then I see the alert with "SUCCESS UNDEFINED". I thought I will get some data I could use on my backend to definitely authentify the user (access token or facebook uid, etc...).
Did I miss something in the way to use Torii with Simple-Auth ?
The promise returned by Session#authenticatedoes not have a value but everything that the torii authenticator resolves with is available via the session as soon as that's authenticated (which it is in your case as the returned promise has resolved). You could access e.g. a token property with
var _this = this;
this.get('session').authenticate(
'simple-auth-authenticator:torii',
'facebook-oauth2'
).then(function(data) {
alert('SUCCESS ' + _this.get('session.token'));
})
You can also inspect the session's content property (which you shouldn't use otherwise as it's private but for debugging that's ok) to find out what's available:
console.log(this.get('session.content'))
I am trying to configure a basic ember-cli app using authentication via ember-cli-simple-auth and want to have a dedicated 'guest' login page and a different 'admin' login page (authorizing to different serverTokenEnpoint's).
I have the 'guest' page working, i.e. if a user tries to browse to a protected route (page) then they are redirected to the default /login route and can login Ok.
What I can't figure out is how to have a user that browse's to /admin/xyz route that they then get redirected (using to /admin/login which in turn will authenticate to a different serverTokenEnpoint to the default.
Can anyone point me in the right direction to achieve the above?
Thanks.
an example protected 'guest' route file looks like:
FILE: /app/routes/protected.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin);
And the environment configs contain:
FILE: /app/config/environment.js
ENV['simple-auth'] = {
authorizer: 'simple-auth-authorizer:oauth2-bearer',
store: 'simple-auth-session-store:local-storage',
crossOriginWhitelist: ['http://www.domain.com/token',
'http://www.domain.com'
]
};
I even tried to override the default authenticationRoute in my /app/routes/admin.js file like below but did not work:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
authenticationRoute: 'admin.login'
});
So to simplify the process following Marco's suggestion I now have:
Note: AT the moment this does not work.. #marcoow do you have any thoughts where im going wrong?
This is using ember-cli with the follow firebug output:
AuthenticatorBase A (unknown mixin) ***<- IS this expected????***
CustomAuthenticator B (unknown mixin)
DEBUG: -------------------------------
DEBUG: Ember : 1.7.0
DEBUG: Ember Data : 1.0.0-beta.9
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.11.1
DEBUG: Ember Simple Auth : 0.6.4
DEBUG: Ember Simple Auth OAuth 2.0 : 0.6.4
DEBUG: -------------------------------
and if I put my manual override code back in see previous answer it will work, but since I want to use the same oauth2 authentication just to a different URL, I like the idea of just being able to override the TokenEndpoint with a custom authenticator.
file: app/initializers/simple-auth-admin.js
import AuthenticatorBase from 'simple-auth-oauth2/authenticators/oauth2';
var CustomAuthenticator = AuthenticatorBase.extend({
serverTokenEndpoint: AppchatENV['simple-auth-admin'].serverTokenEndpoint,
serverTokenRevokationEndpoint: AppchatENV['simple-auth-admin'].serverRevokationTokenEndpoint,
refreshAccessTokens: AppchatENV['simple-auth-admin'].refreshAccessTokens
});
console.log("AuthenticatorBase A ",AuthenticatorBase);
console.log("CustomAuthenticator B ",CustomAuthenticator);
export default {
name: 'simple-auth-admin',
before: 'simple-auth',
initialize: function(container) {
container.register('simple-auth-authenticator:admin', CustomAuthenticator);
}
};
But the above shows an error of "AuthenticatorBase A (unknown mixin)"
and then in
file: app/controllers/admin/login.js
import Ember from 'ember';
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:admin'
}
and for the configs...
file: config/environment.js
ENV['simple-auth-admin'] = {
serverTokenEndpoint: "http://www.domain.com/admintoken",
serverTokenRevokationEndpoint: "http://www.domain.com/admintoken/revoke",
refreshAccessTokens: true
};
EDIT:
so by setting: in file: app/initializers/simple-auth-admin.js
import AuthenticatorBase from 'simple-auth-oauth2/authenticators/oauth2';
var CustomAuthenticator = AuthenticatorBase.extend({
serverTokenEndpoint: MyappENV['simple-auth-admin'].serverTokenEndpoint,
serverTokenRevokationEndpoint: MyappENV['simple-auth-admin'].serverRevokationTokenEndpoint,
refreshAccessTokens: MyappENV['simple-auth-admin'].refreshAccessTokens
});
console.log("AuthenticatorBase.serverTokenEndpoint =",AuthenticatorBase.serverTokenEndpoint);
console.log("CustomAuthenticator.serverTokenEndpoint =",CustomAuthenticator.serverTokenEndpoint);
console.log("MyappENV['simple-auth-admin'].serverTokenEndpoint = ",MyappENV['simple-auth-admin'].serverTokenEndpoint);
export default {
name: 'simple-auth-admin',
before: 'simple-auth',
initialize: function(container) {
container.register('simple-auth-authenticator:admin', CustomAuthenticator);
console.log("[at container.register] CustomAuthenticator.serverTokenEndpoint = ",CustomAuthenticator.create().get('serverTokenEndpoint'));
}
};
I get output of:
AuthenticatorBase.serverTokenEndpoint = undefined
CustomAuthenticator.serverTokenEndpoint = undefined
MyappENV['simple-auth-admin'].serverTokenEndpoint = http://www.domain.com/oauth2/admintoken
[at container.register] CustomAuthenticator.serverTokenEndpoint = http://www.domain.com/oauth2/admintoken
Am I misuderstanding what AuthenticatorBase.extend () is doing? I thought it would allow you to override some variables or functions?
EDIT 2:
file: app/controllers/admin/login.js
import Ember from 'ember';
var $ = Ember.$;
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:admin',
init: function(){
console.log('INIT LOGIN CONTROLLER', this.get('session'));
this._super();
},
actions: {
authenticate: function() { // (data)
console.log("LoginController clicked");
$('#nameBtn').ladda().ladda('start');
console.log(this.get('session'));
console.log('this.authenticator = ', this.authenticator);
var _this = this;
this._super().then(null, function(data) {
console.log('LOGIN GOT BACK: ', data);
$('#nameBtn').ladda().ladda('stop');
if(data.error !== undefined && data.error !== "") {
_this.set('data', {error: data.error});
}
});
}
}
});
This results in an ajax to www.domain.com/token rather than the expected www.domain.com/admintoken
OK, after a lot of coding in circles and trial and error and with a lot of help from:
https://github.com/simplabs/ember-simple-auth/blob/master/examples/6-custom-server.html
this is how I achieved what I wanted...
1) Setup the enpoints as variables in the environment file (simple-auth-admin is the name i chose for my admin authenticator)
File: /app/config/environment.js
ENV['simple-auth-admin'] = {
serverTokenEndpoint: "http://www.domain.com/admintoken",
serverTokenRevokationEndpoint: "http://www.domain.com/admintoken/revoke",
refreshAccessTokens: true
};
2) Create the actual authenticator as an override in an initialiser Note: in this case the CustomAuthorizer is not actually used and make sure you replace AppNameENV with your app name, so if your app was called bob it would be BobENV.
File: /app/initializers/simple-auth-admin.js
import Ember from 'ember';
import AuthenticatorBase from 'simple-auth/authenticators/base';
import AuthorizerBase from 'simple-auth/authorizers/base';
var CustomAuthorizer = AuthorizerBase.extend({
authorize: function(jqXHR, requestOptions) {
if (this.get('session.isAuthenticated') && !Ember.isEmpty(this.get('session.token'))) {
jqXHR.setRequestHeader('Authorization', 'Token: ' + this.get('session.token'));
}
}
});
var CustomAuthenticator = AuthenticatorBase.extend({
tokenEndpoint: window.AppNameENV['simple-auth-admin'].serverTokenEndpoint,
tokenRevokationEndpoint: window.AppNameENV['simple-auth-admin'].serverRevokationTokenEndpoint,
refreshAccessTokens: window.AppNameENV['simple-auth-admin'].refreshAccessTokens,
init: function(){
console.log("CUSOTMM AUTH INIT ",window.AppNameENV['simple-auth-admin'].serverTokenEndpoint);
this._super();
},
restore: function(data) {
console.log('AdminAuth - restore');
return new Ember.RSVP.Promise(function(resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
} else {
reject();
}
});
},
authenticate: function(credentials) {
console.log('AdminAuth - authenticate',credentials);
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ grant_type: 'password', username: credentials.identification, password: credentials.password, session: { identification: credentials.identification, password: credentials.password } }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.access_token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
invalidate: function() {
console.log('AdminAuth - invalidate');
var _this = this;
return new Ember.RSVP.Promise(function(resolve) {
Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
resolve();
})
});
}
});
export default {
name: 'simple-auth-admin',
before: 'simple-auth',
initialize: function(container) {
console.log("OVERRIDES : ", window.AppNameENV['simple-auth-admin']);
container.register('simple-auth-authenticator:admin', CustomAuthenticator);
container.register('simple-auth-authorizer:admin', CustomAuthorizer);
}
};
3) The I setup a redirect route to admin/login for any protected pages ( this example is for /admin/dashboard)
File: /app/routes/admin/dashboard.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
authenticationRoute: 'admin.login',
actions: {
authenticateSession: function() {
this.transitionTo(this.authenticationRoute);
}
}
});
4) Then configure the admin controller to use the new custom authenticator
File: /app/controllers/admin/login.js
import Ember from 'ember';
var $ = Ember.$;
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
//import Session from 'simple-auth/session';
export default Ember.Controller.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:admin',
});
All of which seems a bit heavy handed when all i really wanted to do was have the authentication for the /admin/login point to a different serverendpoint. Marco, is there a way of overriding just those variables and therefore extend the simple-auth-oauth2 authorizer?
The routes you're defining are necessary of course.
Since your authorizer and authenticator for the admin area seem to be customized as well those are necessary as well of course. If you used the plain OAuth 2.0 ones for the admin area as well you could drop the authorizer and change the authenticator to
import AuthenticatorBase from 'simple-auth-oauth2/authenticators/oauth2';
var CustomAuthenticator = AuthenticatorBase.extend({
serverTokenEndpoint: 'http://www.domain.com/admintoken',
serverTokenRevokationEndpoint: 'http://www.domain.com/admintoken/revoke'
});
Every time Ember Simple Auth enforces authentication (usually when a user accesses an authenticated route while the session is not authenticated), it calls the ApplicationRouteMixin's authenticateSession action. The best option you have is to override that and somehow decide whether to transition to the admin or the guest login page from there. If you have e.g. your admin pages namespaces in an /admin route, you could also override the authenticateSession on the AdminRoute and transition to the admin login page from there while the default implementation in the ApplicationRoute transitions to the guest login page.
For the authenticators it's probably best to use the default OAuth 2.0 authenticator with its serverTokenEndpoint to authenticate guests and extend another authenticator from that that authenticates admin against a different serverTokenEndpoint.
I use Ember Simple Auth with the following settings:
Note: I use Ember App Kit.
app.js
// init Ember.SimpleAuth
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.setup(application, { // #todo at version 0.1.2 of Ember-simple-auth, add container variable
crossOriginWhitelist: ['http://customdomain'],
// store: Ember.SimpleAuth.Stores.LocalStorage, // default now
authenticationRoute: 'article.login'
});
}
});
export
default App;
a simple loginController
(took it mostly from Ember App Kit Simple Auth)
var CustomAuthenticator = Ember.SimpleAuth.Authenticators.OAuth2.extend({
serverTokenEndpoint: 'http://customdomain/access_token/',
makeRequest: function(data) {
return Ember.$.ajax({
url: this.serverTokenEndpoint,
type: 'POST',
data: {
grant_type: 'password',
username: data.username,
password: data.password
},
dataType: 'json',
contentType: 'application/x-www-form-urlencoded'
});
}
});
var LoginController = Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, {
authenticator: CustomAuthenticator,
actions: {
// display an error when logging in fails
sessionAuthenticationFailed: function(message) {
console.log('sessionAuthenticationFailed');
this.set('errorMessage', message);
},
// handle login success
sessionAuthenticationSucceeded: function() {
console.log('sessionAuthenticationSucceeded');
this.set('errorMessage', "");
this.set('identification', "");
this.set('password', "");
this._super();
}
}
});
export
default LoginController;
So far so good, I can authenticate a user thought a login form. However when I press F5, I have to login again. The LocalStorage adapter is empty. So the question is what do I need to persist the token and session?
Note: I cannot update to ember-simple-auth 0.1.2, bower cannot find the new version. Seems that the github version of https://github.com/simplabs/ember-simple-auth-component is not up to date.
Edit:
I have updated my code as follows:
app.js
// init Ember.SimpleAuth
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.Authenticators.OAuth2.reopen({
serverTokenEndpoint: 'http://customdomain/access_token'
});
Ember.SimpleAuth.setup(container, application, { // #todo at version 0.1.2 of Ember-simple-auth, add container
crossOriginWhitelist: ['http://customdomain'], // #todo remove when live
// store: Ember.SimpleAuth.Stores.LocalStorage,
authenticationRoute: 'article.login'
});
}
});
export default App;
loginController:
var LoginController = Ember.Controller.extend(Ember.SimpleAuth.LoginControllerMixin, {
// authenticator: CustomAuthenticator, // not needed anymore
actions: {
// display an error when logging in fails
sessionAuthenticationFailed: function(message) {
this.set('errorMessage', message);
},
// handle login success
sessionAuthenticationSucceeded: function() {
this.set('errorMessage', "");
this.set('identification', "");
this.set('password', "");
this._super();
}
}
});
export default LoginController;
I haven't used the oauth2 authenticator before (just a custom one for my backend that I wrote) but I think the same concepts should apply.
When you refresh the page ember-simple-auth makes a call to the restore method of the oauth2 authenticator that you are using. The restore method is looking for a property called 'access_token' to confirm that the user has already authenticated with your server. Does your REST API return a property called access_token when you authenticate with the endpoint at http://customdomain/access_token/? If not, you want to make sure this is happening or you will encounter the refresh issue you're having. Here's the restore method in the oauth2 authenticator provided with ember-simple auth:
restore: function(properties) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
// It looks for the 'access_token' property here which should have been set
// by the authenticate method if you returned it from your REST API
if (!Ember.isEmpty(properties.access_token)) {
_this.scheduleAccessTokenRefresh(properties.expires_in,
properties.expires_at,
properties.refresh_token);
resolve(properties);
} else {
reject();
}
});
}
Additionally, I think in your sessionAuthenticationSucceeded action you need to return true. Otherwise the action won't propagate up to the ember-simple-auth ApplicationRouteMixin (unless you are not using that mixin or don't depend on its sessionAuthenticationSucceeded method in which case it doesn't matter).
This should be fixed with 0.1.2: github.com/simplabs/ember-simple-auth/releases/tag/0.1.2
I also just updated github.com/simplabs/ember-simple-auth-component