I'm using Ember Simple Auth Devise v 0.6.4 in an Ember-cli app.
I can log in fine but when I refresh the page the session is lost. (Tested in Firefox and Chrome.)
Right after logging in, inspecting the localStorage shows the session and after refreshing localStorage is empty.
Here's what's in the local storage when I sign in:
The problem is that you have neither user_token nor user_email in the session which are required for the session to be authenticated. So as soon as you reload the page the authenticator's restore method rejects the session. Also without user_token and user_email the authorizer is not going to actually authorize any requests.
You'll need to change your server side devise setup as described here.
I have run into the same issue with simple-auth-devise.
The problem was that inconfig/environment.js the identificationAttributeName was overridden.
ENV['simple-auth-devise'] = {
identificationAttributeName: 'email'
};
By doing so, it no longer matched the data returned by Users::SessionsController on successful authentication, taken from the ember-simple-auth-devise Readme:
data = {
token: user.authentication_token,
user_email: user.email
}
The attribute names must match, so the solution is to use the identificationAttributeName in the JSON returned by the controller:
data = {
token: user.authentication_token,
email: user.email
}
Like marcoow pointed out, it is all in the implementation of the Devise authorizer restore() method.
I'm experiencing the same issue, e.g. my session is getting nuked on refresh.
This is undesired behavior, and for me at least doesn't appear to have anything to do with server side devise setup.
No requests are being sent to the server, it's just a matter of keeping the session alive by using the cookies which should be checked first.
I had this issue as well. It turns out that the restore method in the authenticator did not take into account the resource name.
In particular, changing the line indicated here: https://github.com/simplabs/ember-simple-auth/blob/master/packages/ember-simple-auth-devise/lib/simple-auth-devise/authenticators/devise.js#L95
as follows:
if (!Ember.isEmpty(propertiesObject.get(_this.resourceName)[_this.tokenAttributeName]) && !Ember.isEmpty(propertiesObject.get(_this.resourceName)[_this.identificationAttributeName])) {
solved the problem.
Note that my local storage looked like:
{"secure":{"authenticator":"simple-auth-authenticator:devise","user":{"id":1,"email":"test#gmail.com","created_at":"2015-07-20T22:30:47.966Z","updated_at":"2015-07-23T17:45:41.874Z","authentication_token":"7Uv6LysQ2h3x-P4WUMmU","token":"7Uv6LysQ2h3x-P4WUMmU"}}}
As a result, this required the additional changes in the config/environment.js
ENV['simple-auth-devise'] = {
identificationAttributeName: 'email',
resourceName: 'user',
tokenAttributeName: 'authentication_token',
crossOriginWhitelist: ['*']
};
Changing bower_components/ember-simple-auth/simple-auth-devise.amd.js is what allowed me to see that this indeed was my problem.
Related
I am writing a web-based app that uses AWS Cognito as the authentication service. I use 'aws-amplify' to implement the client-app.
I am using Auth.updateUserAttributes() to update a custom attribute of users on Cognito. However, I found that the call of this function would clear all the Cognito-related items, including idToken, refreshToken, and accessToken stored in localStorage. As a result, the web app behaves like sign-out.
Here is the code about Auth's configuration
Amplify.configure({
Auth: {
userPoolId: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
region: process.env.REACT_APP_AWS_COGNITO_REGION,
userPoolWebClientId: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID,
storage: window.localStorage,
authenticationFlowType: 'CUSTOM_AUTH',
},
});
and the code I wrote to update the user's attribute. (I followed the example code from the amplify docs https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#managing-user-attributes)
let user = await Auth.currentAuthenticatedUser();
console.log(JSON.stringify(localStorage)); // it outputs the localstorage with idToken,
// refreshToken, accessToken and other items
// start with 'CognitoIdentityServiceProvider'
const result = await Auth.updateUserAttributes(user, {
'custom:attributes_1': '123456789',
});
console.log(result); // OUTPUT: SUCCESS
console.log(JSON.stringify(localStorage)); // Only 'amplify-signin-with-hostedUI'.
// idToken, refreshToken, accessToken and
// other items were gone. No key, no value.
After the last line, I could not interact with the web page anymore. If I refreshed the web page, I found I had signed out and had to sign in again.
It was still the same if I changed the storage for Auth from localStorage to sessionStorage.
Here are my questions:
Is this kind of behavior normal? Does Auth.updateUserAttributes() leads to a force sign-out?
If it's true, is there any way to avoid a mandatory sign-out?
If it's not, what's wrong with my code or configuration? Or should I do some specific configuration for the Cognito service?
Thanks a lot!
Well, I figured it out after reading the source code of aws-amplify.
Behind the call of Auth.userUpdateAttributes, amplify will finally call CognitoUser.refreshSession(refreshToken, callback, clientMetadata) (https://github.com/aws-amplify/amplify-js/blob/f28918b1ca1111f98c231c8ed6bccace9ad9e607/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1446). Inside this function, amplify sends an 'InitiateAuth' request to Coginito. If an error of 'NotAuthorizedException' happens, amplify calls clearCachedUser() that delete everything I mentioned in my question from the localStorage.
There was an error of 'NotAuthorizedException' happening and reported by the network work monitor of Chrome Browser'. I thought it was generated after the sign-out-like behavior. However, it turned out to be triggered because no deviceKey was passed to the request's parameters.
So the whole story was:
I set remember device options in Cognito;
I used 'CUSTOM_AUTH' as the authentication flow type;
When a user successfully signed in to my application, Cognito didn't give the client the deviceKey because of the 'CUSTOM_AUTH' authentication flow type.
When Auth.userUpdateAttributes() was called, CognitoUser.refreshSession() was called behind it. It attached no deviceKey to Cognito when it sent ana request to asked Cognito to refresh the token. The Cognito rejected the request with an error of 'NotAuthorizedException'. The CognitoUser.refreshSession() handled the error and called clearCachedUser() to delete the stored tokens and other info from the localStorage.
My final solution is to turn off the remember device option since I have to use 'CUSTOM_AUTH' as the authentication flow type according to my application's functional requirements.
According to https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pool-remembered-devices/, the remember device function only works when the authentication flow type is set as 'USER_SRP_AUTH'.
I am trying to figure out if the following behavior is the expected.
In my case, I am logged in as a user with username: testuser
I make a PATCH request from my Blazor SPA to the following URL https://myidentityserver/scim2/Me. This is the location the WSO2 IS is located.
The PATCH request,containing a password change and some other patched info,completes successfully and Status Code:200 OK is returned.
Immediately after the aforementioned PATCH request, a call to https://myidentityserver/oauth2/token is made and Status Code: 400 is returned containing the grant_type: refresh_token, client_id: myid, refresh_token:xxxxxx, meaning that the token is invalid.
In my understanding this is the expected behavior as the information that were present in the token have now been altered due to the PATCH and token should be invalidated.
WSO IS version: 5.11.0
The default behavior of IS is when the password is updated by the user, all the sessions are terminated, and all the tokens are revoked.
However, there is an option to skip the current session/token from being terminated/revoked at password update.
In order to enable that feature, you need to add the following config in <wso2is-5.11.0-home>/repository/conf/deployment.toml file and restart the server.
[identity_mgt]
password_update.preserve_logged_in_session=true
Refer to the git issue for more details:
https://github.com/wso2/product-is/issues/9461
Answering my own question that I made in the comments
The answer was provived from the relevant WSO2 IS slack chat
'As per the question, the PATCH request contains a password change also. So in that case the exiating sessions will be invalidated as you mentioned. But for other claim updates which will be done from SCIM2 will not invalidate the existing sessions'
In my api, I have a /users endpoint which currently shows (eg address) details of all users currently registered. This needs to be accessed by the (Ember) application (eg to view a user shipping address) but for obvious reasons I can't allow anyone to be able to view the data (whether that be via the browsable api or as plain JSON if we restrict a view to just use the JSONRenderer). I don't think I can use authentication and permissions, since the application needs to log a user in from the front end app (I am using token based authentication) in the first instance. If I use authentication on the user view in Django for instance, I am unable to login from Ember.
Am I missing something?
UPDATE
Hi, I wanted to come back on this.
For authentication on the Ember side I'm using Ember Simple Auth and token based authentication in Django. All is working fine - I can log into the Ember app, and have access to the token.
What I need to be able to do is to access the user; for this I followed the code sample here https://github.com/simplabs/ember-simple-auth/blob/master/guides/managing-current-user.md
I have tested the token based authentication in Postman, using the token for my logged in user - and can access the /users endpoint. (This is returning all users - what I want is for only the user for whom I have the token to be returned but that's for later!).
The question is how to do I pass the (token) header in any Ember requests, eg
this.store.findAll('user') .... etc
This is clearly not happening currently, and I'm not sure how to fix this.
UPDATE
Fixed it. Turns out that the authorize function in my application adapter was not setting the headers, so have changed the code to set the headers explicitly:
authorize(xhr) {
let { access_token } = this.get('session.data.authenticated');
if (isPresent(access_token)) {
xhr.setRequestHeader('Authorization', `Token ${access_token}`);
}
},
headers: computed('session.data.authenticated.token', function () {
const headers = {};
if (this.session.isAuthenticated) {
headers['Authorization'] = `Token ${this.session.data.authenticated.token}`
}
return headers;
})
Ember is framework for creating SPAs. These run in the browser. So for Ember to get the data, you have to send the data to the browser.
The browser is completely under the control of the user. The browser is software that works for them, not for the owner of the website.
Any data you send to the browser, the user can access. Full stop.
If you want to limit which bits of the data the user can read from the API, then you need to write the logic to apply those limits server-side and not depend on the client-side Ember code to filter out the bits you don't want the user to see.
I don't think I can use authentication and permissions, since the application needs to log a user in from the front end app (I am using token based authentication) in the first instance. If I use authentication on the user view in Django for instance, I am unable to login from Ember.
This doesn't really make sense.
Generally, this should happen:
The user enters some credentials into the Ember app
The ember app sends them to an authentication endpoint on the server
The server returns a token
The ember app stores the token
The ember app sends the token when it makes the request for data from the API
The server uses the token to determine which data to send back from the API
I looked at the GitHub OAuth API and saw that one requirement to send to the endpoint is state.
Sending it is trivial - but how do you validate that the state you sent is the same one you’re receiving?
I thought of using browser caching but it seems like it’s for views of Django and improving performance.
I thought of sending a CSRF token as the state but it seems it’s meant for forms you generate.
In short, how do you validate state in Django? Is there a Pythonic way in Django to do so?
The user's session is the best place to store variables that are session related, which is the case here.
So generate your state and store it into the user's session:
request.session['github_state'] = state
return render(<template with github link>, context={'state': state})
Then when you receive the user's authorised GET request:
if request.session.get('github_state') and not request.GET.get('state') == request.session['github_state']:
# abort here
else:
code = request.GET.get('code')
# POST request with code to GitHub to fetch the access token
I'm checking that the session variable isn't empty/None, otherwise both might be empty and the check would pass.
The page to login to our application is a jsp hosted on another machine. I have managed to get requests firing to this machine by modifying authenticated-route-mixin by allowing window.location.replace to be called if the route start with http.
beforeModel(transition) {
if (!this.get('session.isAuthenticated')) {
Ember.assert('The route configured as Configuration.authenticationRoute cannot implement the AuthenticatedRouteMixin mixin as that leads to an infinite transitioning loop!', this.get('routeName') !== Configuration.authenticationRoute);
transition.abort();
this.set('session.attemptedTransition', transition);
debugger;
if (Configuration.authenticationRoute.startsWith('http')) {
window.location.replace(Configuration.authenticationRoute);
} else {
this.transitionTo(Configuration.authenticationRoute);
}
} else {
return this._super(...arguments);
}
}
This is working but when I am redirected back to my application, ember-simple-auth thinks I am no longer logged in and redirects be back to the remote machine, which then sends me back to the application in an infinite loop.
Obviously I need to set something to let ember-simple-auth know that it it is actually logged in. Why is it not doing this automatically? What am I doing wrong?
I am pretty new to oAuth so I could be missing some basic setting here.
Here is the URL.
ENV['ember-simple-auth'] = {
authenticationRoute: 'https://our-server.com/opensso/oauth2/authorize?client_id=test-client-1&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fsecure'
};
Instead of modifying the AuthenticatedRouteMixin, I'd recommend handling your app-specific login in an Authenticator-- the key configuration primitive that Ember Simple Auth provides as part of its public API.
To the best of my understanding, on first loading the app, and checking to see if a user is authenticated, Ember Simple Auth will use the restore method, defined as part of the Authenticator API.
You can return a promise from restore that resolves or rejects to indicate whether the user is authenticated. How you check this is an implementation detail of your auth system.
I don't know how you're storing credential(s) on the client (would be great if you could provide more detail), but here's an example flow, using cookies for authentication:
Ember boots, ESA attempts to restore the session.
restore makes a simple AJAX request to a secured, "dummy" resource on your Java server-- and checks if it gets back a 200 or a 401.
We get a 401 back. The user isn't authenticated, so reject in the Promise returned from restore.
Let ESA redirect the user to your authentication route. Ideally, don't override the AuthenticatedRouteMixin-- instead, use the beforeModel hook in the authentication route to send users to your JSP login page.
The user correctly authenticates against the JSP form.
In its response, your Java server sets some kind of encrypted, signed session cookie (this is how it generally works with Rails) as a credential. In addition, it sends a redirect back to your Ember app.
Ember boots again, ESA calls restore again.
restore pings your Java server again, gets a 200 back (thanks to the cookie), and thus resolves its Promise.
ESA learns that the user's authenticated, and redirects to the 'route after authentication'.
Keep in mind that, at its core, ESA can only indicate to the client whether the backend considers it 'authenticated' or not. ESA can never be used to deny access to a resource-- only to show something different on the client, based on the last thing it heard from the backend.
Let me know if any of that was helpful.