Handle redirection in async calls - ember.js

I have an app where on initial load the user is redirected to sign in page. Once the user is authenticated, he is then taken to homepage. While verifying the user, an HttpOnly cookie is also set to the browser. So now to remove the hassle for user to login every time he refreshes the app or opens it in another tab. I'm sending a authenticate request back to the server inside beforeModel hook of my application route. This will verify the user and page loads as expected. However if the server response has 401 (either because user logged out or cookie expired) the app will redirect him to login page. Everything works fine and as expected.
But there are few things tricky to resolve.
If the user gives path as /login I need to wait for the authenticate request to complete before deciding on to render the login template or to redirect to home screen if he is already logged in.
Also wait for validate call to complete before executing the model hook in target url. I saw the modal request going to server even when the response was 401 for authenticate call.
A good example is in github page, where once you log in and go to /login page they take you to your home page.

After some experiment with routes.
I came up with this solution.
On every url request( either by refresh or entered path ), store the url unless it is for /login. This would be done in most likely beforeModel hook of application route.
Then on each topmost route of route hierarchy that an authenticated user can browse through have it redirect to login route.
On login route make the appropriate call to the API for authentication. If user is logged in transition to the stored url or to homepage if nothing is stored. If however 401 comes back load the template for the same.
Make sure that your async call for the API is made in one of beforeModel, model or afterModel hook, and you have the call to API in a return statment, otherwise the template will load no matter.
NOTE:
1. You can have the url stored in some service which will be accessible throughout app. You can also have the service store other login information for future use.
2. In point (2), make sure to do some check before redirecting to /login in case of unnecessary redirection. Suppose if the route was transitioned internally rather than through page load. 3. Don't forget to handle 401 case in your authenticate request, or it may just display nothing on page with an error in console.
Hope this helps

Related

Display content based on sign in state via OAuth

I have my api behind a third party OAuth (ex. google, twitter, etc). When a user hits /api/login, they are redirected into the OAuth flow and then sent back to my callback /api/login/callback. I store their login info and then send back a same-site http only session cookie to validate their user id. On subsequent requests, I retrieve that session cookie to get their user info and then perform requests using the OAuth token stored earlier.
Now, I want to create a frontend to go with my backend REST api. When a user goes to my / route they get a generic about page along with a sign in button. The sign in button redirects to my /api/login route and eventually back to my /api/login/callback. Now, the callback will redirect again to the / route. Subsequent requests made will have the session cookie attached and will go through.
My problem arises in that I don't know how to communicate to my frontend that my user is logged in. Because my session cookie is http only I can't access the cookie on page load to render a different UI for logged in users.
Some ideas I've had:
Hit up a /api/me URL that returns 401 or 200 depending on if the session cookie was sent. The problem with this is that this will leave the frontend in a limbo while the request is resolving.
Make the cookie not https only. However, I've read online that this makes it vulnerable to XSS attacks.
Send a second, non https only cookie as well to show that the session cookie exists. If this cookie is tampered with the worst that can happen is that a user will receive a 401 error later down the road when they make an API call without the session cookie.
Put something in local storage or in a cookie before the request to signify a user hit log in and check for it on page load. However, I won't know if the login succeeded or not.
Create a second page specifically for unsigned in users on the / route. Then, the callback can redirect to /signed-in. However, how will my /signed-in route know if a user navigated there or if the server redirected them? (ex. if the user autocomplete's the browser bar to the /signed-in route after their session expires)
Out of all these the third approach seems the most viable (the second cookie). However, this seems like a very trivial problem that someone has solved before. What am I missing here?
Note: I don't want to use ssr here. If I was using ssr I could simply just check for the session cookie server-side on the / route and reply with a different HTML template.
Edit: I could combine ideas 3 and 4. Put something in a store before sign in. If sign in fails, have my server redirect to a /fail page. If not, redirect to /. Then, / can reload the store on page load. /fail would also delete the stored item so that a user who failed can't just immediately go back to / and see they are logged in. The only unauthorized people who would see my user ui on / would be
Users who close out of the page during their login (never finish their login so never redirected to /fail to delete the store)
Users who revoke their OAuth token. This will have to be caught later down the road when my server receives a 401.
I could also add in a third "authorizing" state. I would set this before login. On page load with the authorizing state, I'd make a request in the background to validate that the user finished signing in. If I get a 401 from my server I'd have to move the user out of the authorized page. It wouldn't be nice but it'd occur less often than if I didn't use the store.
Before hitting sign in set a temporary loading value inside a store (cookie, framework store, localstorage, etc).
If the callback URL receives a failure value, redirect to the /fail route. /fail will set a failure value inside the store and redirect to /.
If the callback URl receives a success value, redirect to the /success route. /success will replace the temporary value with a success value.
On page load, read the store.
If the store is empty its a new user.
If the store has a temporary value, they never get redirected after the callback. Show a toast about an error and then display the sign in page.
If the store has a failure value, they failed the OAuth. Again, show a toast and display the sign in page.
If the store has a success value, everything went right. Show the user UI.
Eventually, the user may want to revoke their token. If they do, my app will not know until I make a request to a protected api endpoint with their token. If so, just pass the 401 to my frontend. I can show a modal saying they are unauthorized and then replace the success store value with an empty value.

Django Sessions: Correct way to get logged in user data from server?

I have my API set up using SessionAuthentication. Once a user logs in, I redirect them to their profile page in React. Once they are redirected to their profile page, I want to make a REST call to retrieve their profile data and insert it in the proper location on the page. I see a couple ways I can do this:
When a user logs in, put their User ID into the Response object (DRF) and then store that in the client somewhere (Redux store, session storage, or local storage). Then when they are redirected to the login page, make a REST call to /users/users_id.
With Django sessions the logged in user is automatically tied to each request. So do I even need to follow Rest here? I can make a call to /users, and if the user is authenticated, return their data.
I would appreciate any help with this. Thank you.
With SessionAuthentication, after a successful login, the browser saves a sessionId cookie for that domain (or ip:port) automatically. Sending a request will send that cookie from the same domain no matter with Django or React, and authenticate the user, making your request.user a user.
You can check for the cookie when you inspect the page -> Application -> Cookies -> Your domain -> sessionId
Basically, you can login via Django and it will login you with React as well. No need to store anything manually. Just use the same domain for both.

Using ember-simple-auth when the login page is on another system

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.

EmberJS - Handling 3rd party redirect authentication

I'm using ember-simple-auth for my Ember app, but I don't have an API endpoint to authenticate users, rather it does a page redirect to the form and signs a user in, then redirects back to my app. (I don't own the authentication)
After authentication, it gets redirected back to me, so I know on the server side when a user has been successfully authenticated. How do I manually authenticate the users' session when they are redirected back to my app?
Currently I did a hack to write two cookies: ember_simple_auth:access_token and ember_simple_auth:authenticator.
I think setting up the session store manually is an ok solution in this scenario as that will trigger the session to be restored after the redirect (which is on startup of the Ember application). I'd maybe configure a custom authenticator that redirects to the external login page in the authenticate method. That way you have that redirect centralized and it will also be triggered automatically whenever Ember Simple Auth automatically enforces session authentication (e.g. from the AuthenticatedRouteMixin).

Django redirect to previous page after external OAuth login

I am using Twitter OAuth to login users. The login takes users to Twitter and upon successful OAuth returns them to a specified url. From this url I would like to redirect users back to the page they were on before logging in.
What is a good way to do this?
Two ways:
Craft your OAuth URL so it sends them back to the right page, or at least says next=url in the querystring. This is most reliable but can break (and does look ugly but who's copying and pasting OAuth URLs anyway?)
Store a session containing the last requested "real" page. I say "real" like that because I don't count any auth/registration pages as real. So every hit, check to see what URL they're on, if it's not auth-related, store it in session. When they hit your OAuth-auccess page, redirect them to the session value. You can do this in a context processor or some middleware. Requires cookies and logout will nuke it.
i am using redirect url in twitter auth url and its working for me ..