Open adamdawkins opened 8 years ago
I wonder if this is more like the approach you were expecting for authorization in Mantra @arunoda? (Thinking of your auth logic tutorial here.).
Rather than try and do anything specific in each component / container that needs authorization, I wrap the content()
passed to layout in an AuthRender
component that only wraps the content if authRequired
is also passed to the router.
Inspired largely by @Makstr's auth composer in Mantra dialogue:
routes.jsx
FlowRouter.route('/', {
name: 'feed',
action() {
mount(publicContext, {
content: () => (<Feed/>),
authRequired: true
});
}
});
Layout:
import _authRender from './auth_render.jsx';
import authContainer from '../../_accounts/containers/auth';
const AuthRender = authContainer(_authRender);
const Layout = ({content = () => null, authRequired = false}) => (
<div>
<header>
<h1>Mantra Twitter</h1>
<UserControls/>
</header>
<main>
{authRequired ? <AuthRender content={content} /> : content()}
</main>
</div>
);
AuthRender
render() {
const {loggedIn, content} = this.props;
return loggedIn ? content() : <Login/>;
}
I've updated the title because although my redirect bug was weird, I don't think we want to be re-directing the URL from within a container.
@arunoda It'd be good to know if this is the type of direction you'd envision for Mantra with login auth, or if I'm way off!
@adamdawkins I did sort of the same thing in a test app with an outer container checking auth state and either showing the content or login component. But yes, I can see why you'd want some sort of confirmation regarding this (or some other opinion).
Then there's more control for authorisation with permissions or roles (perhaps using https://github.com/alanning/meteor-roles), maybe handled inside a component using control statements (https://github.com/AlexGilleran/jsx-control-statements) or a wrapper (container) checking the permission and choosing what to display.
Maybe @tomitrescak can share some thought, I think he's done an app with auth too.
As per my solution. I have created an auth container that handles meteor data:
export const composer = ({context, roles}, onData) => {
const { Meteor } = context();
if (Meteor.user()) {
onData(null, {
authInProcess: Meteor.loggingIn(),
currentUser: Meteor.user(),
roles
});
} else {
onData(null, {
authInProcess: Meteor.loggingIn()
});
}
return null;
};
export default (component: any) => composeAll(
composeWithTracker(composer),
useDeps()
)(component);
Then, I have wrapped the following layout component with the container:
const Layout = ({authInProcess, roles, content = () => null }) => (
<div>
<header>
<h1>Auth Example</h1>
</header>
<div>
<If condition={authInProcess}>
<Loading />
<Else />
<span>
<If condition={roles}>
<If condition={Roles.userIsInRole(Meteor.userId(), roles)}>
<span>{content()}</span>
<Else />
<Prohibited />
</If>
<Else />
<If condition={Meteor.user()}>
<span>{content()}</span>
<Else />
<Prohibited />
</If>
</If>
</span>
</If>
</div>
</div>
);
export default Layout;
It is a bit difficult to understand at first, but once you start dissecting those if else statements, it becomes pretty clear. Simply, it shows the <Loading />
component, while the authorisation is in process. Then, it allows you to specify a list of roles that can view a given content or, if no roles were specified it checks, whether a user is logged in.
In order to specify the list of roles you need to call the mount function withOptions
as following:
const {mount, withOptions} from `react-mounter`;
// define your admin content mounting function
export let mountAdmin = withOptions({
roles: ["admin"]
}, mount);
// call it anywhere where you need to mount admin content only
mountAdmin(UserLayoutCtx, {
content: () => (<GroupMailView />)
});
I hope this helped
Thanks both, very helpful, I was just thinking exactly the same thing, that I need to contain the whole layout with the auth container. I'm about to get into some roles too, so this is going to come in very helpful @tomitrescak https://github.com/tomitrescak!
On Tue, 23 Feb 2016 at 22:11 Tomas Trescak notifications@github.com wrote:
As per my solution. I have created an auth container that handles meteor data:
export const composer: IKomposer = ({context, roles, clearErrors}, onData) => { const { Meteor } = context();
if (Meteor.user()) { onData(null, { authInProcess: Meteor.loggingIn(), currentUser: Meteor.user(), roles }); } else { onData(null, { authInProcess: Meteor.loggingIn() }); }
return clearErrors; }; export default (component: any) => composeAll( composeWithTracker(composer), useDeps() )(component);
Then, I have wrapped the some layout component with the container:
import React from 'react';import Navigation from './navigation.jsx'; const Layout = ({authInProcess, roles, content = () => null }) => (
``` Auth Example
```{content()} {content()} );export default Layout;
It is a bit difficult to understand at first, but once you start dissecting those if else statements, it becomes pretty clear. Simply, it shows the
component, while the authorisation is in process. Then, it allows you to specify a list of roles that can view a given content or, if no roles were specified it checks, whether a user is logged in. In order to specify the list of roles you need to call the mount function withOptions as following:
// define your admin content mounting functionexport let mountAdmin = withOptions({ roles: ["admin"] }, mount); // call it anywhere where you need to mount admin content onlymountAdmin(UserLayoutCtx, { content: () => (
) }); I hope this helped
— Reply to this email directly or view it on GitHub https://github.com/kadirahq/mantra/issues/89#issuecomment-187907711.
@tomitrescak out of interest, how did you get the JSX Control Statement plugin working with Mantra? I tried adding to the .babelrc, but have just found an issue in here that explains that's only used for unit tests, and that the Meteor ecmascript package is still doing the compiling.
Ha! I just ran into the very same issue and reported it in #95 ;( I am using the meteor-webpack solution. So I'm using meteor-webpack to package my project. It looks like that this will not be possible with pure Meteor ;(
I still feel like there's some good use-cases for being able to have login, and post login a redirect of some sort. Which is very difficult to do if we're doing all of authentication at the component level.
For example, if I want admin users to see a different screen post login to non-admins, I could just render different components for them, but I'd much rather be able to send to admins to '/admin' on login, and other users '/profile' (or whatever).
Could something like this work in the FlowRouter action?
action() {
if (Meteor.userId()) {
mount(LayoutCtx, {
content: () => (<Home/>)
});
} else {
FlowRouter.go('/login');
}
}
I'm trying to solve this as well. I originally implemented this in Meteor 1.2 where I had the auth logic in the MainLayout with ReactMeteorData mixin. Followed a similar approach below and quickly realized that this won't work as it's not reactive. The initial render works fine and shows the login/register page if the user is not logged in. However, if they login, MainLayout does not re-render the contents based on the current route.
import React from 'react';
import {Meteor} from 'meteor/meteor';
import Header from './header.jsx';
import Footer from './footer.jsx';
import Authentication from './authentication.jsx';
const MainLayout = ({content = () => null }) => {
const currentUser = Meteor.user();
const layout = (
<div id="main-layout">
<Header/>
<div className="ui grid container" id="main-container">
<div className="twelve wide column">
{content()}
</div>
</div>
<Footer/>
</div>
);
return currentUser ? layout : <Authentication />;
}
export default MainLayout;
Should we be redirecting if user is not logged in or just present a login form if user is not logged in. I was under the impression the latter is preferred but following the discussions above and on other issues, everyone is talking about redirects.
Whats the preferred approach here?
I think the prefered approach is very much to just render the login form. Personally, I'm not convinced there isn't still a benefit to the redirect approach... but I'm going with the 'render the login form' approach for now.
On Sat, 27 Feb 2016 at 00:06 Mirza Joldic notifications@github.com wrote:
I'm trying to solve this as well. I originally implemented this in Meteor 1.2 where I had the auth logic in the MainLayout with ReactMeteorData mixin. Followed a similar approach below and quickly realized that this won't work as it's not reactive. The initial render works fine and shows the login/register page if the user is not logged in. However, if they login, MainLayout does not re-render.
import React from 'react'; import {Meteor} from 'meteor/meteor';
import Header from './header.jsx'; import Footer from './footer.jsx'; import Authentication from './authentication.jsx';
const MainLayout = ({content = () => null }) => { const currentUser = Meteor.user(); const layout = (
``` {content()}); return currentUser ? layout : <Authentication />; } export default MainLayout; Should we be redirecting if user is not logged in or just present a login form if user is not logged in. I was under the impression the latter is preferred but following the discussions above and on other issues, everyone is talking about redirects. Whats the preferred approach here? — Reply to this email directly or view it on GitHub https://github.com/kadirahq/mantra/issues/89#issuecomment-189520427.
I think the prefered approach is very much to just render the login form.
I agree. I haven't done any super big project, but it's helped that you already have the next "layout state" almost there, after login success
, so no need to redirect or anything.
Then again, nothing is set in stone. You aren't doing anything "wrong" if you use the router in places to redirect. If that works for you, if the code is clear, there's no problem.
Looking at my code, what I've actually got here is actually a bit of a hybrid. The component renders the login form, but I'm setting whether I want it to or not at the route level.
Router:
import React from 'react';
import {mount} from 'react-mounter';
import MainLayout from './components/main_layout.jsx';
import AuthContainer from './containers/auth';
import Home from './components/home.jsx';
import UsersNew from './containers/users.new';
const AuthLayout = AuthContainer(MainLayout);
...
const AuthLayoutCtx = injectDeps(AuthLayout);
const MainLayoutCtx = injectDeps(MainLayout);
Then, when I want to authenticate, I use the AuthLayoutCtx
, and when I don't, I just use the MainLayoutCtx
FlowRouter.route('/', {
name: 'home',
action() {
mount(MainLayoutCtx, {
content: () => (<Home/>)
});
}
});
FlowRouter.route('/users/new', {
name: 'users.new',
action() {
mount(AuthLayoutCtx, {
content: () => (<UsersNew/>)
});
}
});
To make this work with the one layout, I just pass authRequired: true
from the auth container, and check for it in the layout:
{ authInProcess ? <p>Loading</p> : null }
{ !authRequired || currentUser ? content() : <Login/>}
I'm sure this will evolve as I need roles etc, and it does feel like something that we should just standardise more or less for Mantra apps, as it's such a basic requirement!
There are different ways to deal with this problem.
First create a set of auth components (or permissions). Let's say one of that is, AfterLoggedIn
.
We can use it in a layout or another component like this:
(This code is not tested, just entered here)
<AfterLoggedIn>
<MyOtherContainer />
</AfterLoggedIn>
This is how can write this container:
export function composer({context}, onData) {
const {Meteor} = context();
if (Meteor.user()) {
onData(null, {loggedIn: true});
}
}
export const Component = ({children}) => (children);
return composeAll(
composeWithTracker(composer),
useDeps();
)(Component);
We can simply import the composer and use it. This is really great, if we need to use the component always with user permissions. This is how we do it.
import {authComposer} from 'meteor-auth';
export function composer() {
// my composer logic
}
return composeAll(
composeWithTracker(authComposer),
composeWithTracker(composer),
useDeps();
)(MyComponent);
We can build a set of common containers/composers like this and ship it via NPM. Then we could reuse them in all apps. This will be a great project.
This second approach is sort of what I've done, wrap the same layout in the auth composer when required. Expect someone else will be better at normalising it though! On Sat, 27 Feb 2016 at 14:22, Arunoda Susiripala notifications@github.com wrote:
There are different ways to deal with this problem. Approach 1
First create a set of auth components (or permissions). Let's say one of that is, AfterLoggedIn.
We can use it in a layout or another component like this:
(This code is not tested, just entered here)
This is how can write this container:
export function composer({context}, onData) { const {Meteor} = context(); if (Meteor.user()) { onData(null, {loggedIn: true}); } }
export const Component = ({children}) => (children);
return composeAll( composeWithTracker(composer), useDeps(); )(Component);
Approach Two
We can simply import the composer and use it. This is really great, if we need to use the component always with user permissions. This is how we do it.
import {authComposer} from 'meteor-auth'; export function composer() { // my composer logic } return composeAll( composeWithTracker(authComposer), composeWithTracker(composer), useDeps(); )(MyComponent);
We can build a set of common containers/composers like this and ship it via NPM. Then we could reuse them in all apps. This will be a great project.
— Reply to this email directly or view it on GitHub https://github.com/kadirahq/mantra/issues/89#issuecomment-189637465.
when using authComposers, would the the composer functions handle any redirection that needed to take place? I ask because sometimes you don't want to render a "login" component instead present a redirect.
export const composer = ({context}, onData) => {
const {Meteor} = context();
if(!Meteor.user()){
FlowRouter.go('/login/');
}
};
edit: just read that any redirects should be handled in actions.
@fallenpeace you could do it. But I would like to apps to show a link to the login page rather redirecting him to the login page. It's a good UX pattern and works best for us.
I agree fallenpeace's idea.but It can't redirect in composers.I think these is ugly that displaying login link in some pages.
@arunoda Is this more or less what you had in mind in your comment? https://github.com/sungwoncho/meteor-auth
@sungwoncho Yes. That's the starting point. You can provide more composer functions like
Or others as needed.
We are currently, moving our issues to Mantra Talk. So, it's better discuss about this topic here: https://talk.mantrajs.com/t/how-do-i-set-up-authorization-is-mantra/44
I've put my authorization logic in an action, which I call in the composer, as per #62, #65.
The strangest thing is happening though, the URL is changing to '/login', but the 'feed' component is still rendering.
Is this an 'order' thing? Do I need to 'stop' something?
The repo is here: https://github.com/adamdawkins/mantra-twitter/commit/17aed61eea791936c445ad7977ae4f49f066dde7