Sapper - protected routes (route guard)
Asked Answered
F

4

6

My question is very simple. How do you prevent, e.g. non authorized user, to enter specific routes in sapper?

user.svelte

<script>
    import { onMount } from 'svelte';

    onMount(() => {
      if(!authenticated)
        window.history.back()
      });
</script>

Is there any option to run some code before mounting to the DOM?

How do you solve this kind of problem?

Thank you.

Fulllength answered 29/9, 2019 at 7:59 Comment(1)
it is best to do it in the server.js file,Reubenreuchlin
R
8

I can't say it's the right thing. It's what I do in my SPAs. If I want to protect all routes of my app. I create following in _layout.svelte top file.

<script context="module">

    import {ax} from './_parts/Helper.svelte'
    import {admin, adminName} from './store'
    import {goto} from '@sapper/app'

    export async function preload(page) {
        try {
            const {data} = await ax.get('/admin/is-logged-in')
            adminName.set(data)
            admin.set(true)
        } catch (e) {
            admin.set(false)
        }
    }
</script>

<script>
    import Login from './admin/login.svelte'
    import {loading} from './store.js'
</script>

<main>
{#if $admin}
     <slot></slot>
{:else}
     <Login />
{/if}
</main>

ax is nothing magic. It's just configured axios. '/admin/is-logged-in' is where you check session at backend.

Rota answered 29/9, 2019 at 15:23 Comment(5)
This works good, but imagine e.g. some user roles, admin, editor, writer,... how to protect different routes based on your role?Fulllength
Well, at abstraction level. After login you can fetch the user role from db and save that role inside store.js. After that you can protect component using if-else condition blocks.Rota
The way I've implemented this feels wrong to me myself. The proper approach should be using the session argument passed to preload. See this sapper.svelte.dev/docs#ArgumentRota
@YousufIqbalHashim how do you access the store in preload() if it executes on the server?Famed
Good Idea! I did the same with flutter where I created a widget that checks if the user is logged in. If so it returns its child. Otherwise it returns a loging widget.Lazarolazaruk
F
1

I think that I had basically the same problem. I posted question (with solution) here on SO and solved it the same day with help from this issue from Sapper GitHub with my own "auth redirecting middleware".

Festschrift answered 6/11, 2019 at 11:42 Comment(0)
T
1

Let me generalize that problem. We should implement session state synchronization between client and backend. What the best way to do that? It's a difficult question. It depends.

For example, if you try to protect something valuable when the remote users just try to access restricted areas it's pretty simple. Just redirect to sign in/signup page. But what if the one remote user logout and the second user try to log in at the same application at the same browser before reloading from backend? We quickly jump to the logic trap. So how to correctly manipulate with DOM tree accordingly to the remote session state?

Transition between routes

Probably the best way to do that, just control transitions between routes. In sapper, you should do that by exporting the preload function for every module.

So instead place logic into _layout.svelte (so easy but not sapper way in my opinion). We should create a general module route-guards.js as an example. Then we should import it into every module.

The rest of an answer:

    // route-guards.js
    export async function transitionControl(self, page, session) {
        const result = await self.fetch('/is-logged-in');  // self for support server side rendering
        const response = await result.json();
    
        if (!response && page.path !== '/login') {
            return self.redirect(302, '/login');
        }
    }

    <!-- any module.svelte -->
    <script context="module">
        import { transitionControl } from "./route-guards";
    
        export async function preload(page, session) {
            await transitionControl(this, page, session);
            // Do another what you want before page load
        }
    </script>

Please keep in mind that the sync states between two things never be easy as you want. OMG web development so difficult. Thanks, everyone for pay attention.

Trevor answered 10/10, 2020 at 17:45 Comment(0)
S
0

Thanks Yousuf for your answer. I tinkered around with it and came up with an alternative solution that redirects to a login page if the user is not logged in.

I also did not want all routes to be protected, since the user must be able to login and register. Therefore I use the page object to determine the route the user wants to navigate to and I also check the session object if a login token is present (e.g. a JWT). This check could be modified to suit your needs.

_layout.svelte

<script context="module">
  const publicRoutes = ['/login', '/register'];

  export async function preload(page, session) {
    if (publicRoutes.indexOf(page.path) === -1 && !session.token) {
      this.redirect('302', 'login');
    }
  }
</script>

... The rest of your component here ...
Superfamily answered 4/8, 2020 at 17:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.