Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to setup Ember app to have secure by default routes #578

Closed
eliotsykes opened this issue Jul 3, 2015 · 25 comments
Closed

Document how to setup Ember app to have secure by default routes #578

eliotsykes opened this issue Jul 3, 2015 · 25 comments

Comments

@eliotsykes
Copy link

Thanks for providing ember-simple-auth. I'd be happy to contribute the docs I'm about to suggest, I'll just need some guidance on how to achieve the result.

Some apps have routes that all require authentication, except for the login page. From the ember-simple-auth documentation it seems as if every route file in the app would need to extend AuthenticatedRouteMixin explicitly like so:

import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin);

It'd be great to have some documentation that explained how to have it so every route by default extends AuthenticatedRouteMixin so the above snippet does not need to be put in every route file.

Along with this documentation it'd be handy to explain how to configure a route that doesn't need authentication (such as the login form route).

@clekstro
Copy link
Contributor

clekstro commented Jul 4, 2015

One straightforward possibility (without requiring any code changes here) would be to create a new AuthenticatedRoute base route class that you would inherit your other routes from. That would spare you the mixin part of the boilerplate at least, although you'd still have to import that file.

It sounds like you're not asking just for documentation of the solution above, but a configuration hook within the project itself? Something like secureAllRoutes?

I think the solution above is actually probably better than adding a configuration hook here. If you're absolutely sure you'll never want to expose another route that doesn't require authentication (no /about, /contact, /faq, etc.), then the hook would save you time. But it comes at the cost of flexibility. The moment you do need that route, you have to fall back to this anyway, which would require you to manually edit your routes, which actually sounds like more work in the end.

@eliotsykes
Copy link
Author

Huge thanks for the detailed response @clekstro.

It is documentation I was hoping to get out of this issue as I suspected this was a common scenario for developers that was solved but just hadn't made it to the docs yet.

Thanks for bringing up the public route scenario, sorry I neglected to mention it originally. It would be needed to have a couple of public routes (e.g. /about page) that is accessible to all users (anonymous and authenticated users). The route breakdown is along these lines:

  • Public routes for all users:
    • 3+ routes e.g. /, /about, /contact. These routes will rarely change.
  • Private routes for authenticated users:
    • 10+ routes e.g. /user/*, /issues, /discussions, /todos, etc. New routes will frequently be added to this list as the application grows.
  • Unauthenticated routes for anonymous users:
    • 1 route /login. Unlikely to change ever.

This post from the Ember forum describes the sort of solution I was aiming to discover and document: http://discuss.emberjs.com/t/specifying-whether-or-not-routes-should-check-for-authentication/4097/4

If I've understood, a custom AuthenticatedRoute is the way to achieve this.

@marcoow
Copy link
Member

marcoow commented Jul 6, 2015

You can also define an /internal route in which you mix in the AuthenticatedRouteMixin and then route all the routes that require authentication under that.

@jrhe
Copy link

jrhe commented Jul 6, 2015

I achieve this with the following monkey patch to route. All routes are authenticated and then when you want a route which isn't authenticated you just set unauthenticated to false on that route.

import Ember from 'ember';
import AuthConfig from 'simple-auth/configuration';

Ember.Route.reopen({
  // By default, all routes are authenticated. i.e. they will need to be signed in
  // To make a route non authenticated, set authenticated to false.
  //
  // If a user tries entering an non authenticated route and they are authenticated,
  // they will be redirected to the route which is displayed after authentication.
  // This is useful for login pages and pages you don't want the user to see when they
  // are signed in.
  //
  // This creates a strict dichotomy of pages which the user can see when they are
  // signed in and signed out which may not be appropriate. It might be worth separating
  // out unauthenticated route into its own flag.

  authenticated: true,

  beforeModel(transition) {
    // TODO Double check this

    this._super(transition);
    // We don't want to authenticate the application route as this gets called before every route.

    if (this.routeName === 'application') {
      return;
    }

    const sessionAuthenticated = this.get(AuthConfig.sessionPropertyName).get('isAuthenticated');

    // Authenticated route and currently not authenticated

    if (this.get('authenticated') && !sessionAuthenticated) {
      transition.abort();
      this.get('session').set('attemptedTransition', transition);
      transition.send('authenticateSession');

    // Unauthenticated route and currently authenticated

    } else if (!this.get('authenticated') && sessionAuthenticated) {
      transition.abort();

      // Direct them back to the route after authentication

      this.transitionTo(AuthConfig.routeAfterAuthentication);
    }
  }
});

@eliotsykes
Copy link
Author

Thanks for taking the time to give all this code and guidance, planning to try out some of these things shortly.

@thermokarst
Copy link

@marcoow: Do you know if the /internal scheme requires reorganizing pods to physically exist within the internal route directory structure (using ember-cli with pods enabled)? It would be ideal to not move things around in a pre-existing pod-based project.

Thank you so much for all of your hard work with this project, it is certainly appreciated!

@marcoow
Copy link
Member

marcoow commented Jul 10, 2015

@thermokarst: not sure how that would affect apps using pods actually - have never used pods. Would be cool if you tried and left a short notice here how that worked for others as a reference.

@thermokarst
Copy link

@marcoow: Thanks --- I played with this a little bit, and found that I did need to reorganize my source tree a bit, and also update my link-to references to include the parent for the nested route (the resolver was unable to find my routes once the pods were moved into protected/ until prepending protected.). Also, I had to specify a value for routeAfterAuthentication in my config that included the protected. prefix, otherwise the transition after login died. Following is a not-so-short summary:

Test environment: ember-cli 1.13.1, Ember 1.13.3, Ember Data 1.13.5.

Original

Source structure

app
└── pods
    ├── bar
    │   ├── route.js
    │   └── template.hbs
    ├── baz
    │   ├── route.js
    │   └── template.hbs
    └── foo
        ├── route.js
        └── template.hbs

Router

Router.map(function() {
  this.route('foo'); // unprotected
  this.route('bar'); // protected
  this.route('baz'); // protected
});

Unprotected route

// app/pods/foo/route.js

import Ember from 'ember';

export default Ember.Route.extend({});

Protected route

// app/pods/bar/route.js

import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {});

Link to protected route

{{!-- app/pods/foo/template.hbs --}}

{{#link-to 'bar' bar}}
    Link to "Bar"
{{/link-to}}

Modified (/protected route)

Source structure

app
└── pods
    ├── foo
    │   ├── route.js
    │   └── template.hbs
    └── protected
        ├── bar
        │   ├── route.js
        │   └── template.hbs
        ├── baz
        │   ├── route.js
        │   └── template.hbs
        └── route.js

The route.js under app/pods/protected is for handling any setup that needs to be done for the protected routes (e.g. setting up a currentUser).

Router

Router.map(function() {
  this.route('foo'); // unprotected
  // path: "/" keeps the endpoints from prepending 'protected'
  this.route('protected', { path: "/" }, function() {
    this.route('bar'); // protected
    this.route('baz'); // protected
  });
});

Unprotected route

// app/pods/foo/route.js

import Ember from 'ember';

export default Ember.Route.extend({});

Protected route

// app/pods/protected/route.js

import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {});

Because bar and baz are nested in protected, they get the authentication hooks by default, no need to mix in AuthenticatedRouteMixin.

// app/pods/protected/bar/route.js

import Ember from 'ember';

export default Ember.Route.extend({});

Link to protected route

{{!-- app/pods/foo/template.hbs --}}

{{#link-to 'protected.bar' bar}}
    Link to "Bar"
{{/link-to}}

@marcoow
Copy link
Member

marcoow commented Jul 20, 2015

Closing this as it's actually not an issue. It can still serve as a reference as it should be easily findable in the issues search.

@marcoow marcoow closed this as completed Jul 20, 2015
@eliotsykes
Copy link
Author

Thanks @jrhe for the code you contributed above. For an Ember CLI project, do you have a recommendation for what file you'd put this code in? #578 (comment)

@jrhe
Copy link

jrhe commented Jul 26, 2015

@eliotsykes I have it in a file at app/monkey-patches/route.js which I import in my app/app.js file first thing usingimport 'my-app/monkey-patches/route';`. This seems to be the best place to put it as it ensures that its loaded before the app is created and therefore any routes are created.

@marcoow Would it be worth adding something like this to ember-simple-auth as an alternative to the protected route method? I don't like the idea of having a protected route as it adds extra cruft to the URLS in apps where everything is protected. I think this is actually quite a common use case.

@eliotsykes
Copy link
Author

Alternative solution for secure by default routes with Simple Auth. Feedback is most welcome.

The goal with this solution was to reuse the existing route mixins and minimize custom code.

// app/routes/application.js

import Ember from 'ember';
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
import UnauthenticatedRouteMixin from 'simple-auth/mixins/unauthenticated-route-mixin';

Ember.Route.reopenClass({
  create() {
    var route = this._super(...arguments);

    var simpleAuthRouteMixinApplied = ApplicationRouteMixin.detect(route) ||
      AuthenticatedRouteMixin.detect(route) || UnauthenticatedRouteMixin.detect(route);

    if (!simpleAuthRouteMixinApplied) {
      AuthenticatedRouteMixin.apply(route);
    }
    return route;
  }
});

export default Ember.Route.extend(ApplicationRouteMixin);

@marcoow
Copy link
Member

marcoow commented Jul 27, 2015

I don't think these reopen based approaches should be part of the library. It seems if people can't live with an authenticated route it's pretty straight-forward to implement sth. like the suggestions above.

@eliotsykes
Copy link
Author

@marcoow Would you consider a PR documenting how to achieve this? If yes, what file do you recommend adding the docs to?

@marcoow
Copy link
Member

marcoow commented Jul 27, 2015

@eliotsykes I think documenting things like this that aren't really on the happy path of the library nor are they really important for the majority of people really bloats the docs. I guess a wiki page with some recipes for common scenarios might be a better place.

@eliotsykes
Copy link
Author

@marcoow thanks! The GitHub wiki for the project appears to be unavailable.

@marcoow
Copy link
Member

marcoow commented Jul 27, 2015

@eliotsykes: enabled it

@eliotsykes
Copy link
Author

@jrhe
Copy link

jrhe commented Jul 27, 2015

Can it not be an alternate happy path? I think its a very common use case that the user is signed in for pretty much everything. Anything which has user accounts and is a fully fledged app seems to lean this way. Dashboards/ admin panels (heroku), IM clients (slack, skype), subscription based (spotify).

I don't like the idea of distributing something with reopenClass as its not transparent enough but how about breaking it into a mixin?

import AuthenticatedByDefaultRouteClassMixin from './mixins/authenticated-by-default-route-class-mixin';
Ember.Route.reopenClass(AuthenticatedByDefaultRouteClassMixin);

@oliverbarnes
Copy link

@jrhe maybe this would be more useful as an addon? So all one would have to do is install it to have protected routes by default. I can see add-ons complementing on the main scenario, keeping the core project from getting bloated, and giving users plug-and-play alternative scenarios

@marcoow
Copy link
Member

marcoow commented Jul 27, 2015

Having an Haddon for that might be the better approach. The core library supports this scenario with adding a /protected route which is also much more comprehensible and less error prone.

@eliotsykes
Copy link
Author

Recipe updated to include new OpenRouteMixin to cover scenarios where pages need to be viewable to all users regardless of authentication, e.g. home page, about page, etc. https://github.com/simplabs/ember-simple-auth/wiki/Recipe:-Defaulting-to-Authentication-Required-Routes

@jrhe
Copy link

jrhe commented Aug 8, 2015

@fridaystreet,

You just override it in the route you don't want to be authenticated. i.e.

extend default Ember.Route.extend({
authenticated: false
});

On 8 August 2015 at 03:13, fridaystreet notifications@github.com wrote:

@jrhe https://github.com/jrhe I like your solution, apologies for the
noob question, but how do you apply the authenticated: false to other
routes?

Cheers
Paul


Reply to this email directly or view it on GitHub
#578 (comment)
.

@robclancy
Copy link

robclancy commented Dec 7, 2017

Just to add to this old thread since it is still linked from the docs.

I don't really like mixins adding functionality like this and in the past used the method with things like open-route-mixin. With moving to engines I decided to go for something a little more towards the libraries defaults but also needing default authed and ability for a route to be "open".
Properties on the route I feel is a lot cleaner that having to use a mixin which could be confusing to new developers thinking it is adding a bunch of functionality when really it's being used as a flag.

import Mixin from '@ember/object/mixin';
import { getProperties } from '@ember/object';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin';

export default Mixin.create({
  create() {
    let route = this._super(...arguments);

    let simpleAuthRouteMixinApplied = ApplicationRouteMixin.detect(route)
      || AuthenticatedRouteMixin.detect(route)
      || UnauthenticatedRouteMixin.detect(route);

    // override if explicit
    if (simpleAuthRouteMixinApplied) {
      return route;
    }

    let { needsGuest, needsAuth } = getProperties(route, ['needsGuest', 'needsAuth']);

    // defaults to needsAuth, to set the route to "open" you set `needsGuest` and `needsAuth` to `false`
    if (needsAuth === undefined && ! needsGuest) {
      needsAuth = true;
    }

    if (needsGuest && ! UnauthenticatedRouteMixin.detect(route)) {
      UnauthenticatedRouteMixin.apply(route);
    }

    if (needsAuth && ! AuthenticatedRouteMixin.detect(route)) {
      AuthenticatedRouteMixin.apply(route);
    }

    return route;
  }
});
// routes/application.js in both engines and app

import Route from '@ember/routing/route';
// (I have this in a shared in-repo-addon so it can be used in app and engine easily
import AuthenticatedByDefaultRouteClassMixin from 'shared/mixins/authenticated-by-default-route-class-mixin';

Route.reopenClass(AuthenticatedByDefaultRouteClassMixin);
// example open route

import Route from '@ember/routing/route';

export default Route.extend({
  needsAuth: false,
});

@jsmestad
Copy link

@robclancy awesome. I really like this method of doing it. Much more obvious

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants