PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0
Showing posts with label single-page-application. Show all posts
Showing posts with label single-page-application. Show all posts

Thursday, October 13, 2022

[FIXED] How should I request API in express SPA implementation?

 October 13, 2022     axios, express, javascript, single-page-application     No comments   

Issue

I am implementing SPA using express server.

Server is sending index.html files for all 'get' request in the following way.

app.get("/*", (req, res) => {
  res.sendFile(path.resolve(__dirname, "/public", "index.html"));
});

By the way, what should I do with the get api request as below? I can't get a request because '/*'

app.get("/:id", (req, res) => {
  console.log(req);
});

I think the order is important, so it was the same even if I changed the two and sent the request. Is there a solution?


Solution

Distributed architecture

Following the Keep it simple and to have a more clean deployment, I advice you to have the spa and the api on different hosts, domains, git repositories, etc

In real scenarios, your spa will consume multiple APIs, so host the spa within one of them is not the best choice

Check this for more detailed answer

  • https://stackoverflow.com/a/71791940/3957754

spa + api = monolith

Anyway if you need to host the spa inside of api, you need to register the api routes before the static route:

app.get("/:id", (req, res) => {
  console.log(req);
});

app.get("/*", (req, res) => {
  res.sendFile(path.resolve(__dirname, "/public", "index.html"));
});


Answered By - JRichardsz
Answer Checked By - Senaida (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Sunday, September 4, 2022

[FIXED] How to perform user registration and authentication between a single page application and a REST API with OpenID Connect

 September 04, 2022     authentication, oauth-2.0, openid-connect, rest, single-page-application     No comments   

Issue

Consider that we have:

  1. An SPA or a statically generated JAMStack website.
  2. A REST API.

The website is being served with nignx that also reverse proxies to our API.

--

It is required that a user should be able to register/authenticate with an identity provider (say, Google) through the OpenID Connect protocol. For the sake of simplicity, let us assume that the user has already registered with our API.

Talking about authentication using OIDC, from what I have read on the subject, the steps you take are the following:

  1. Register the application with the IdP and receive a client id and a secret.
  2. When the user initiates a login (with Google) request on the API ('/api/loginWithGoogle') the API sets a state variable on the request session (to prevent CSRF) and redirects the user-agent to the IdP's login page.
  3. At this page, the user enters their credentials and if they are correct, the IdP redirects the user to the callback URL on the API callback (/api/callback).
  4. The request received on the callback has the state parameter (which we should verify with the one we set on the session previously) and a code parameter. We exchange the code for the identity token with the authorization server/IdP (we also receive access/refresh tokens from the auth server, which we discard for now because we do not want to access any APIs on the behalf of the user).
  5. The identity token is parsed to verify user identity against our database (maybe an email). Assume that the identity is verified.

-- The next part is what's giving me trouble --

  1. The documentation that I have read advises that from here we redirect the user to a URL (e.g. the profile page)and start a login session between the user agent and the API. This is fine for this specific architecture (with both the SPA/static-site being hosted on the same domain).

But how does it scale?

  1. Say I want to move from a session based flow to a JWT based flow (for authenticating to my API).
  2. What if a mobile application comes into the picture? How can it leverage a similar SSO functionality from my API?

NOTE: I have read a little on the PKCE mechanism for SPAs (I assume it works for JAMStack as well) and native mobile apps, but from what I gather, it is an authorization mechanism that assumes that there is no back-end in place. I can not reconcile PKCE in an authentication context when an API is involved.


Solution

Usually this is done via the following components. By separating these concerns you can ensure that flows work well for all of your apps and APIs.

BACKEND FOR FRONTEND

This is a utility API to keep tokens for the SPA out of the browser and to supply the client secret to the token service.

WEB HOST

This serves unsecured static content for the SPA. It is possible to use the BFF to do this, though a separated component allows you to serve content via a content delivery network, which some companies prefer.

TOKEN SERVICE

This does the issuing of tokens for your apps and APIs. You could use Google initially, though a more complete solution is to use your own Authorization Server (AS). This is because you will not be able to control the contents of Google access tokens when authorizating in your own APIs.

SPA CLIENT

This interacts with the Backend for Frontend during OAuth and API calls. Cookies are sent from the browser and the backend forwards tokens to APIs.

MOBILE CLIENT

This interacts with the token service and uses tokens to call APIs directly, without using a Backend for Frontend.

BUSINESS APIs

These only ever receive JWT access tokens and do not deal with any cookie concerns. APIs can be hosted in any domain.

SCALING

In order for cookies to work properly, a separate instance of the Backend for Frontend must be deployed for each SPA, where each instance runs on the same parent domain as the SPA's web origin.

UPDATE - AS REQUESTED

The backend for frontend can be either a traditional web backend or an API. In the latter case CORS is used.

See this code example for an API driven approach. Any Authorization Server can be used as the token service. Following the tutorial may help you to see how the components fit together. SPA security is a difficult topic though.



Answered By - Gary Archer
Answer Checked By - Dawn Plyler (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Monday, April 18, 2022

[FIXED] How can I show data names field in vue-multiselect plugin in edit page?

 April 18, 2022     edit, laravel, single-page-application, vue-multiselect, vue.js     No comments   

Issue

I have a vue laravel SPA, and was working on the edit page of employees page. I already have create employees page and can display data on the vue-multiselect plugin (https://vue-multiselect.js.org/). Right now, I can display the employee id's from an array in vue-multiselect in the edit page. How can I format it to show employee names?

<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<script>
    import Multiselect from 'vue-multiselect'

    export default {
        components: { Multiselect },
        data() {
            return {
                employee: {
                    designation_id: [],
                    position_id: [],
                },
                designation_names: {},
                position_names: {}
            }
        },
        methods: {
            updateEmployee() {
                this.axios
                    .patch(`/api/employees/${this.$route.params.id}`, this.employee)
                    .then((res) => {
                        this.$router.push({ name: 'employees' });
                    });
            },
            async getDesignations(id) {
                const { data } = await this.axios.get(`/employees/${id}/designations`);
                this.$set(this.designation_names, id, data.join(', '));
            },
            async getPositions(id) {
                const { data } = await this.axios.get(`/employees/${id}/positions`);
                this.$set(this.position_names, id, data.join(', '));
            },
        },
        created() {
            this.axios
                .get(`/api/employees/${this.$route.params.id}`)
                .then((res) => {
                    this.employee = res.data;
                });
            for(const row of this.rows) { this.getDesignations(row.id); }
            for(const row of this.rows) { this.getPositions(row.id); }
        },
    }
</script>
<template>
    <form @submit.prevent="updateEmployee">
        <div class="flex space-x-6 md:w-3/4">
            <div class="md:w-3/6 mb-4 flex-1">
                    <label class="block text-gray-700 text-sm font-bold mb-2" for="name">First Name</label>
                    <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline required" name="name" id="name" type="text" placeholder="First Name" tabindex="1" v-model="employee.first_name" />
                </div>
                <div class="md:w-3/6 mb-4 flex-1">
                    <label class="block text-gray-700 text-sm font-bold mb-2" for="name">Last Name</label>
                    <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline required" name="name" id="name" type="text" placeholder="Last Name" tabindex="2" v-model="employee.last_name" />
                </div>
            </div>
            <div class="flex space-x-6 md:w-3/4">
                <div class="md:w-3/6 mb-4 flex-1">
                <label class="block text-gray-700 text-sm font-bold mb-2 required" for="designation_id">Designation</label>
                <multiselect
                    v-model="employee.designation_id"
                    :options="designation_options" 
                    :custom-label="opt => designation_options.find(designation => designation.id == opt).name"
                    :multiple="true"
                    :taggable="true"
                ></multiselect>
                </div>
                <div class="md:w-3/6 mb-4 flex-1">
                    <label class="block text-gray-700 text-sm font-bold mb-2 required" for="position_id">Position</label>
                    <multiselect
                    v-model="employee.position_id"
                    :options="position_options"
                    :multiple="true"
                    :taggable="true"
                ></multiselect>
                </div>
            </div>
            <div class="flex space-x-6 md:w-3/4">
                <div class="md:w-3/6 mb-4 flex-1">
                    <label class="block text-gray-700 text-sm font-bold mb-2" for="basic_pay">Basic Pay</label>
                    <div class="relative rounded">
                    <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                        <span class="text-gray-700">₱</span>
                    </div>
                    <input class="shadow appearance-none border rounded w-full py-2 pl-8 pr-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline required" name="basic_pay" id="basic_pay" type="number" step="any"  placeholder="00.00" tabindex="5" v-model="employee.basic_pay" />
                    <div class="absolute inset-y-0 right-0 flex items-center"><label for="basic_pay" class="sr-only">Basic Pay</label>
                    </div>
                </div>
            </div>
            <div class="md:w-3/6 mb-4 flex-1">&nbsp;</div>
        </div>
        <button type="submit" class="sm:hidden md:flex bg-blue-500 hover:bg-blue-400 text-white font-bold py-2 px-4 border-b-4 border-blue-700 hover:border-blue-500 rounded outline-none focus:outline-none">Update</button>
    </form>
</template>


Solution

I've managed to solve this by retrieving each field, instead of the previous code which I retrieved all in a single object.

this.axios
    .get(`/api/designations/${this.$route.params.id}`)
    .then((res) => {
        this.form.name = res.data.name;
        this.form.description = res.data.description;
    });

Before:

this.axios
    .get(`/api/designations/${this.$route.params.id}`)
    .then((res) => {
        this.form= res.data;
    });


Answered By - Jumar Juaton
Answer Checked By - Candace Johnson (PHPFixing Volunteer)
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

Saturday, February 26, 2022

[FIXED] Laravel Sanctum SPA - how to make sure user is only logged in on one device?

 February 26, 2022     laravel, laravel-sanctum, single-page-application     No comments   

Issue

I'm currently using Santum SPA autentication and noticed that I can login from mozilla and chrome at the same time. This means that users can login from different devices. How do I prevent this with Sanctum SPA Authencation?

The thing is I think I should be able to do this if I use Sanctum Tokens since I can easily check if the user has an existing token.

$tokens = $user->tokens;

However, the documentation says:

You should not use API tokens to authenticate your own first-party SPA. Instead, use Sanctum's built-in SPA authentication features.

The things I'll lose if I use sanctum tokens for SPA:

This approach to authentication provides the benefits of CSRF protection, session authentication, as well as protects against leakage of the authentication credentials via XSS.

or is this outside the scope of Laravel Sanctum?


Solution

After googling I'm convinced that this functionality is not included in Laravel SPA Sanctum out-of-the-box.

But I found this youtube video that made this possile.



Answered By - jove0610
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg

[FIXED] Sanctum SPA Authentication: How to log out from other devices?

 February 26, 2022     laravel, laravel-8, laravel-sanctum, single-page-application     No comments   

Issue

Is this feature included in the SPA Authentication? By reading the docs on Laravel Sanctum, it doesn't look like it, but at the same time, this is a common feature (used if user reset password or if you want only one login instance from a user) so I thought that it must be included...

After searching, I found this:

use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);

but you'll need to uncomment this middleware in the web before you can use this.

'web' => [
    // ...
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    // ...
],

However this only works on web and not on api. So I googled again how to have the same functionality with api and I found this youttube video. Here's what the guy did:

  1. Create his own middleware (ex. AuthenticateSessionSPA.php).
  2. Copy everything in AuthenticateSession and paste it in the new middleware. Change namespace and class name.
  3. Create private variable with value of 'sanctum' (ex. private $driver = 'sanctum')
  4. Replace all $this->auth->getDefaultDriver() with $this->driver. So the code will use the sanctum driver instead.

The file looks like this:

<?php

namespace App\Http\Middleware\Custom;

use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as AuthFactory;

class AuthenticateSessionSPA
{
    private $driver = 'sanctum';

    /**
     * The authentication factory implementation.
     *
     * @var \Illuminate\Contracts\Auth\Factory
     */
    protected $auth;

    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Contracts\Auth\Factory  $auth
     * @return void
     */
    public function __construct(AuthFactory $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (! $request->hasSession() || ! $request->user()) {
            return $next($request);
        }

        if ($this->guard()->viaRemember()) {
            $passwordHash = explode('|', $request->cookies->get($this->auth->getRecallerName()))[2] ?? null;

            if (! $passwordHash || $passwordHash != $request->user()->getAuthPassword()) {
                $this->logout($request);
            }
        }

        if (! $request->session()->has('password_hash_'.$this->driver)) {
            $this->storePasswordHashInSession($request);
        }

        if ($request->session()->get('password_hash_'.$this->driver) !== $request->user()->getAuthPassword()) {
            $this->logout($request);
        }

        return tap($next($request), function () use ($request) {
            if (! is_null($this->guard()->user())) {
                $this->storePasswordHashInSession($request);
            }
        });
    }

    /**
     * Store the user's current password hash in the session.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    protected function storePasswordHashInSession($request)
    {
        if (! $request->user()) {
            return;
        }

        $request->session()->put([
            'password_hash_'.$this->driver => $request->user()->getAuthPassword(),
        ]);
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    protected function logout($request)
    {
        $this->guard()->logoutCurrentDevice();

        $request->session()->flush();

        throw new AuthenticationException('Unauthenticated.', [$this->driver]);
    }

    /**
     * Get the guard instance that should be used by the middleware.
     *
     * @return \Illuminate\Contracts\Auth\Factory|\Illuminate\Contracts\Auth\Guard
     */
    protected function guard()
    {
        return $this->auth;
    }
}

And this works wonderfully, I can now use Auth::logoutOtherDevices($currentPassword) in my api.

So my question is if this is safe? I'm just a jr. dev (6 months) and I'm not that confident in this solution since I found it from someone on the net and not from the docs. I'm wondering maybe the laravel devs didn't implement it because they have reasons like security?

What are your thoughts on this method? If you don't agree with this, how would you implement logging out from other devices in a project that has a SPA frontend and Laravel api?


Solution

Doesn't look like this is included in Laravel Sanctum SPA Authentication. So I just did my original answer found on the youtube video



Answered By - jove0610
Read More
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Older Posts Home
View mobile version

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
All Comments
Atom
All Comments

Copyright © PHPFixing