Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Udev's avatar
Level 2

Securing JWT

I would like to create an API that serves multiple SPAs. I was using Laravel sanctum for AUTH however this will not work with the SPAs having different top level domains. I also would like to avoid Laravel passport as I don't need Auth2.0. I opted to use Tymon/jwt-auth, although I have some security concerns on how to store the JWT token:

  1. Browser visibility

To make the token inaccessible to the Browsers JS, I store it in a http-only cookie.

  1. CSRF protection

To protect against CSRF attacks, I could use same-site=lax when storing the JWT cookie.

  1. Token expiration

Set the token to expire after 15 minutes so it can be refreshed regularly.

Would this be enough to secure the JWT token?

Are there any other security concerns I need to concider?

0 likes
11 replies
vincent15000's avatar

Hello, you have exactly the same problem as mine.

Effectively Sanctum SPA authentication has a constraint : you need to have both frontend and backend in the same top-level domain.

If you want to use Sanctum with different top-level domains, you need to use Sanctum API token authentication.

https://laravel.com/docs/11.x/sanctum#api-token-authentication

I just changed my code to use the Sanctum API token authentication instead of the Sanctum SPA authentication and it works fine.

Furthermore Sanctum API token authentication is similar to JWT, but it's easier to code than the tymon/jwt-auth package.

2 likes
Udev's avatar
Level 2

@vincent15000 Good idea, I agree working with sanctum can be easier. Although with JWT, I can put user info in the payload such that I can have the AUTH server separate from say the Posts API in a microservice architecture.

However, my main concern now is ensuring the token is secure in the frontend. How did you go about securing the sanctum API token in the frontend?

1 like
vincent15000's avatar

@Udev I store the token in the sessionStorage.

If you need a more secure way to keep the token frontend side, you can store it inside a store like pinia. But this way if you reload the page by clicking on the refresh button from the browser, you will lose the store's content and so the connexion.

The only way to keep the connexion while reloading the page is to keep some data in the session storage.

Udev's avatar
Level 2

@vincent15000 Can't malicious js in the browser access the session storage and local storage

1 like
vincent15000's avatar

@Udev When you connect to your facebook or instagram or other websites, a token or a cookie or anything similar is stored in the browser and if somebody has access physically to your browser, it can explore these informations and use them to connect to your favorite websites form any other computer.

If your computer is secure and you don't visit some strange websites, then nobody will copy these informations.

Furthermore a website has only access to his own data stores in the localStorage and in the sessionStorage. It cannot access to the local/sessionStorage of another website.

1 like
Udev's avatar
Level 2

@vincent15000

Is this because of SOP?

What if a user visits "some strange websites"? What are some security concerns I should have here?

1 like
vincent15000's avatar

@Udev The problem remains exactly the same for all websites where you have to access with credentials.

If you visit some strange websites, you can click on some dangereous links that coudl for example install a spyware on your computer and listen to all what you are doing with your computer (user, password, ...) when you visit websites.

martinbean's avatar
Level 80

@Udev I just need to clear up a few things being mentioned in this thread.

  • Yes, storing credentials and sensitive information like API tokens in client-side storage such as sessionStorage is insecure. This is why Sanctum uses cookies: the cookie is encrypted so that even if someone does manage to get the cookie, they can’t read its value, and if it’s tampered with then Laravel will just reject it.
  • Storing a token in a JavaScript store like Pinia is no more secure either. It’s still in client-side storage, in plain text.
  • When connecting to a third-party service like Facebook or Instagram, you’re doing so via OAuth. You will receive an OAuth token for authorisation as the user; you as a developer need to store and use that token securely, though.

Answering your original question, if you have many websites/web apps on different domains, and you want them to authenticate against a single API hosted on an entirely different server, then you probably do want OAuth. You can create OAuth clients for each site that needs to access the API. You would then use the authorisation code grant with PKCE (https://laravel.com/docs/11.x/passport#code-grant-pkce) since it describes your use case exactly:

The Authorization Code grant with "Proof Key for Code Exchange" (PKCE) is a secure way to authenticate single page applications or native applications to access your API. This grant should be used when you can't guarantee that the client secret will be stored confidentially or in order to mitigate the threat of having the authorization code intercepted by an attacker. A combination of a "code verifier" and a "code challenge" replaces the client secret when exchanging the authorization code for an access token.

2 likes
Udev's avatar
Level 2

@martinbean Thanks, OAuth2 seems daunting but I guess I will have to learn it.

1 like
martinbean's avatar

@Udev It’s not too bad if you use Passport. That will add the libraries and endpoints your application needs.

Once you‘ve installed Passport, you’d create individual clients for each individual SPA/website/web app you want to authorise. When a user wants to authorise, they’d be redirected to the app containing the API to authorise, and then redirected back to the SPA or whatever with an OAuth token (and refresh token) for you to use to authorise that user for subsequent API requests.

If the SPAs belong to the same “app” as the API, then you can mark the clients as “first party”. This will skip the authorisation screen (the “Do you authorise App X to use your account on App Y?”-type screen) and just automatically authorise the request. However, you don’t want to do this if the SPA or app or whatever is controlled by a third party. So if an external customer or client is creating a SPA and wants to authorise against your API and users, you want that screen to remain in place.

1 like
Udev's avatar
Level 2

@martinbean Thanks again. I kind of get what you mean with "first party" in relation to grant types. I am reading though the RFC 6749 at the moment to get a better understanding of how 0Auth2 works. Would you have or know of any tutorials/examples on passport (preferably with a nuxt or vue frontend) ?

1 like

Please or to participate in this conversation.