Simplify authentication by separating frontend and backend login

Created on 3 May 2023, over 1 year ago
Updated 10 September 2024, 3 months ago

Motivation

Currently authentication works with a shared cookie domain, e.g. both frontend and backend must be hosted below a common domain and the cookie must be configured to be set on the common base-domain. When setup, everything works easily, but still requires CORS setup to work for the authenticated API requests triggered by client-side navigation.

However, sometimes a common parent domain is not available or it is a security issue to use it since 3rd parties might be able to access it, e.g. this is often the case for development environments like *.gitpod.io or *.pantheon.io

Instead, we could solely rely on authentication API requests + disable SSR when the visitor is authenticated. SSR is not crucial for authenticated requests, since (SEO) bots and first-time hits are usually un-authenticated.

proposed resolution - Proxy API

Option Proxy-API:

  • Add route-rules for proxying /ce-api to the backend
  • Provide Drupal login form on /user/login in the frontend, so the cookie will be set in the frontend
  • People can / will have to login separately on frontend and backend
  • Optionally, also proxy files to go via the frontend, so the backend URL would not leak through at all

Originally proposed resolution

  • Set a cookie "active-session=1" after logging in. Thus must be done with-in nuxt somehow.
  • Add a route /user/login which checks the Drupal login state via "drupal/user/login_status?_format=json" and writes the cookie.
  • * When logged out, redirect to Drupal login with destination=/user/login
  • * When logged in, set the cookie and redirect to frontpage
  • Make nuxt3 setup disable SSR when a cookie with "active-session=1" is present

Option: Add session auto-detection:

  • After the first page load, check for a active-session cookie
  • If no cookie is there trigger a call to "drupal/user/login_status?_format=json" to determine whether the user is logged in
  • Set cookie with 1 when authentication was detected - valid with the browser session, else do not set a cookie. So a full page load always detects login state
πŸ“Œ Task
Status

Fixed

Version

1.0

Component

Code

Created by

πŸ‡¦πŸ‡ΉAustria fago Vienna

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @fago
  • πŸ‡¦πŸ‡ΉAustria fago Vienna
  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    On the frontend it should be possible to do it in nuxt3 as outlined here:

    You need to create a Nitro server middleware. Then you have a way to hook into every server request.
    The middleware function gets event as the first argument.
    Within the middleware function, you can use getCookie from h3 to check for your cookie.
    Then, based on that, you can set event.context.nuxt.noSSR to true.

    Untested code:

    // @/server/middleware/dynamic-no-ssr.ts
    
    export default defineEventHandler((event) => {
        const myCookie = getCookie(event, 'myCookie');
    
        if (myCookie) {
            event.context.nuxt = event.context.nuxt || {};
            event.context.nuxt.noSSR = true;
        }
    });
  • πŸ‡¦πŸ‡ΉAustria fago Vienna
  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    Based upon the latest meeting with Dan and longwave we came up with two alternative solutions, leveraging nuxt proxying:

    Option Proxy-API:

    • Add route-rules for proxying /ce-api to the backend
    • Provide Drupal login form on /user/login in the frontend, so the cookie will be set in the frontend
    • People can / will have to login separately on frontend and backend
    • Optionally, also proxy files to go via the frontend, so the backend URL would not leak through at all

    Option Proxy-All:

    • The same as Proxy-API, but also proxy the editorial-experience (/admin) through the frontend.
    • The challenge here would be to ensure we get a complete list of to be-proxied-paths to the frontend, so the Drupal backend would work correctly.
    • People use the site only from the frontend domain, for adminitration and editing as well.

    It would be nice, if we could figure a way out to allow Drupal to return a list of to-be-proxied routes to nuxt.

  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    Letting this sink, I think the option "Proxy API" is the way go here. For that we need to do https://github.com/drunomics/nuxtjs-drupal-ce/issues/165 and then provide the user-login on the frontend.

  • πŸ‡¦πŸ‡ΉAustria fago Vienna
  • πŸ‡ΊπŸ‡ΈUnited States glynster

    Would it make more sense to use a robust module on the Nuxt side for Auth?

    Such as:
    https://github.com/sidebase/nuxt-auth

    This way the communication, along with permissions and roles can be used?

  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    @glynster: Thanks for your suggestion!

    I'm not a big fan of adding something like nuxt-auth in the frontend, since it complicates the frontend stack, e.g. it becomes harder to support multiple frontends, upgrade nuxt majors, etc. But that said, nothing would speak against supportin this optionally. The advantage would be that it makes the frontend aware of permissions via oauth scopes, right?
    Admin menu would definitely be a nice use-case, but in the end we only need a simple flag in page-api responses that tell us whether the users has backend-access/permission or not. Use-cases for permissions to check vary, so we could add a feature to allow permissions to be sent to the frontend as part of the page-api response. It smells a bit like a (simpler) rebuild of oauth-scopes though.
    Maybe let's add a feature request and work on supporting that in a dedicated ticket?

    As default, I think the cookie based access is fine, we just need to finish https://www.drupal.org/project/lupus_decoupled/issues/3336148 πŸ“Œ Add support for forms Needs review to have user-login forms on the frontend working.

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    @fago I had actually thought of the permissions/roles being added to the API. I was thinking it makes sense to have a current_user section. Much like the response from /user/login. Then you have more flexibility and keeps things very simple. I agree with the complication of Nuxt Auth. As you say once the forms are connected I think this solves a lot of things. Then users can login from either end and all works as needed.

    As to the admin menu then you could have a welcome back or username along with a logout.

    Rock on for

    user-login forms on the frontend working
  • πŸ‡ΊπŸ‡ΈUnited States glynster

    FYI: We have managed to hook in Nuxt Auth for our use case. This enables clients to login through a general form using /user/login and user/logout. We use the rest api We also push the new cookie from the response header to the front end which keep font/back end in sync. As well as that we have a session with access to the standard user info. Within that session we have access to csrf_token so we could perform user/node updates if needed. So far so good!

  • Status changed to Needs work 4 months ago
  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    > As well as that we have a session with access to the standard user info. Within that session we have access to csrf_token so we could perform user/node updates if needed. So far so good!

    That seems very interesting. Not sure it's best as default behavior, since it requires more code in nuxt, but I guess it could be very interesting for use-case where access to the session data is needed/wanted. Could you share how you do it?

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    @fargo we have played a lot with the setup and ended up reverting to the default you have setup. I think it is the right approach as it is exactly what Drupal does.

    So in nuxt for login we just redirect them to Drupal backend and log them in. That sets the session and then when they start editing or viewing content they have access to the admin menu in the front end.

    We just extended the Lupus renderer to include some simple user info and this gave us the ability to add admin capability like this:

    function custom_lupus_ce_renderer_response_alter(array &$data, BubbleableMetadata $bubbleable_metadata, Request $request) {
      // Get the currently logged-in user.
      $user = \Drupal::currentUser();
    
      // Create an array containing the current user's name, uid, and roles.
      $current_user = [
        'uid' => $user->id(),
        'name' => $user->getDisplayName(),
        'roles' => $user->getRoles(),
      ];
    
      // Add the current user array to the data.
      $data['current_user'] = $current_user;
    
      // Fetch the site configuration.
      $site_config = \Drupal::config('system.site');
      $site_info = [
        'name' => $site_config->get('name'),
        'slogan' => $site_config->get('slogan'),
        'mail' => $site_config->get('mail'),
      ];
    
      // Add the site info to the data.
      $data['site_info'] = $site_info;
    
      // Remove 'meta' attribute from 'content' if it exists.
      if (isset($data['content']['meta'])) {
        unset($data['content']['meta']);
      }
    }
    

    I would love to share what we did on the Nuxt end for admin features as I think it has been super helpful. Bottom line this is all possible due to the session.

    I love what you guys are doing and opening up the flexibility of Nuxt as the frontend has been awesome.

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    This gives you and idea on larger device and smaller.

  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    interesting! thanks a lot for sharing, glynster!

    I've been toying with the ideas of
    * adding some session/basic permission information
    * adding drupal toolbar/navigation to the frontend
    to the frontend also.

    I've added
    ✨ Add basic session information to responses Active
    ✨ Add Drupal admin navigation to the frontend Active
    for those.

  • πŸ‡ΊπŸ‡ΈUnited States glynster

    Yes, your thoughts align perfectly with what we had in mind. That’s why we decided to revert to a simpler approach. This highlights the beauty of Lupus, where we consistently rely on Drupal to excel at what it does best. Now that you’ve created separate tasks, I’ll respond to those accordingly.

  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    https://www.drupal.org/project/lupus_decoupled/issues/3471132 πŸ“Œ Support user login on the frontend domain Active is done, so things are ready!

    The user login works great via the path /user/login?destination=/ from the frontend. However, when leaving out the destination parameter the frontend login redirects to the backend, what is not optimal. So let's add an issue to make this nicer.

    Meanwhile, let's simply add a frontend vue component that shows a simple Login link in the frontend.

  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    I've updated the project template and its documentation the reflect the new login procedure:
    https://github.com/drunomics/lupus-decoupled-project/commit/7de95b2f40f8...

    There is nothing atm which documents that the logins are separated, will make sure to reflect this when going over the docs-site. For now, people will notice, but it's important to show a Login link in the frontend (in the demo), since it's not clear how to login in the frontend else.

  • Status changed to Fixed 3 months ago
  • πŸ‡¦πŸ‡ΉAustria fago Vienna

    Main work is done, so let's call this done and implement remaining things in follow-ups:

    * The demo needs to be updated generally: https://www.drupal.org/project/lupus_decoupled/issues/3473236 πŸ“Œ Use CE v3 and new login by default Needs work
    * πŸ“Œ Add Login link toggle to demo frontend Active

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024