Headless WordPress Authentication with Native Cookies
Вставка
- Опубліковано 22 лип 2024
- Learn how to authenticate users on your decoupled, frontend JavaScript app using the same native auth cookies that WordPress uses.
There's also a blog post for this video:
developers.wpengine.com/blog/...
Presenter: Kellen Mace - / kellenmace
CONTENTS:
00:00 - Intro
00:56 - App Overview
03:56 - Local Project Setup
07:10 - How this Auth System Works
09:45 - Security & HttpOnly Cookies
11:24 - useAuth() Hook
13:31 - AuthContent & UnAuthContent Components
16:41 - Log In
19:53 - Log Out
22:36 - Navigation
23:28 - Password Reset
33:14 - New User Sign-up
39:55 - User Profile
43:55 - Create Post page
49:48 - Shared Cookies & Admin Access
54:26 - How to Use in Production
55:39 - Outro
CODE REPOS:
Next.js app: github.com/kellenmace/headles...
Port of the app to Gatsby.js: github.com/kellenmace/headles...
PLUGIN LINKS:
WPGraphQL: www.wpgraphql.com/
WPGraphQL CORS: github.com/funkhaus/wp-graphq...
Headless WordPress Email Settings: github.com/kellenmace/headles...
Headless WordPress Admin Access: github.com/kellenmace/headles...
DE{CODE} LINKS:
Follow all of our content here: developers.wpengine.com
DE{CODE} Twitter: / wpedecode
What questions do you have on implementing headless WordPress authentication using WP's native cookies? Let us know!
@TOP 10 You can fire off a `viewer` GraphQL request from your Angular app, similar to the one I show at the 12:00 point in this video. If you get info about the user back in the response, you'll know they're logged in and can then fetch data or perform mutations meant for authenticated users in your Angular app. Thanks for watching! 👍
Thanks for this detailed video, I have one question, I was trying to test my real production wordpress installation endpoint for the NEXT_PUBLIC_WORDPRESS_API_URL, but using the local front end app in next js, I'm getting the SUCCESS response in the network tab, but it seems that the cookie is not being set and app is not logging in. It's not possible to use a real domain graphql endpoint to test my local next js app authentication? Or am I missing some configuration?
I'm getting "Failed to fetch" with CORS error when I logout, login works fine. It happens Gatsby app also. Any advice please?
Thank you for the great explanations! This helped me out a lot!
In the video you appear to have the SameSite cookie attribute set to None which does make you vulnerable to CSRF attacks in this instance. However, I noticed that the WPGraphQL CORS plugin now allows the SameSite cookie mode to be set to Lax or Strict, which effectively eliminates CSRF threats.
Awesome Job on this project.
hey , any ideas on rest api ?
seems like wp_set_auth_cookie uses lax mode
so auth cookie does not set
I did play with it to set the cookie but still
functions like is_user_logged_in does not work
Very nice and instructive video! Have you tried, by any chance, to implement ACF and update those fields in the profile with a mutation?
For those who get trouble in IOS, Safari etc...
The first query (logIn) is sending the email / password as expected, but is not setting the auth cookie as it should do:
As a consequence, the second refetchquery (getUser) is returning the following response:
"data": {
"viewer": null
},
This is because Apple doesn't allow cross-site cookies (related StackOverFlow thread)
The short-term solution (for testing in dev mode on Safari) is to disable this option from Safari settings:
Safari > Settings > Site tracking > Prevent Cross-Site Tracking.
It will allow you to use the app, but it won't fix the issue for all other Safari / iOS users...
The long-term solution is to host both the WordPress (back-end) & the webapp (front-end) on the same domain (e.g. each one on a different sub-domain).
Great video...i want to make windows desktop application for my wordpress website..can i do user athentication for that?
Can you show how we can do auth for fetching entries data form grafity form?
Will this work on react-native (expo) app?
I'm looking to try out this method. Looks great. However, is the Safari issue with not allowing cross-site cookies still a problem? Would this prevent the headless site from being on a different domain?
Love this video and want to try this out. I am still wrapping my head around microservice architecture and wondered if there is any benefit, or is it possible to break this into say a seperate nodejs rest api utilizing apollo and the graphql queries to handle auth and OAuth for your headless wordpress site? Thank you!
Thanks Josh! Glad you found it helpful! Yeah, as an alternative to this approach, you could outsource authentication to a separate service, including a commercial service like auth0 if you wanted to.
this is fanttastic !!
unfortunately this doesn't work when the wordpress is in a local installation (docker or local by flywheel)
I don't know why viewer is alway null. Is there a know solution to set up local by flywheel for example ?
the ting is i need to make some ajax request in wordpress so not easy to test with a production wordpress site
Login is not working for me. wp_logged_in cookie not getting set as secure, samesite=none. any solution?
same here
Did you solve this? I'm having the same issue
same here
Same issue here. did you guys figure it out? Mark cross-site cookies as Secure to allow setting them in cross-site contexts
Wow! This is really advancing headless WordPress.
How difficult would it be to add 2-factor authentication with this setup? There are a handful of WordPress plugins for this
Hey Ian! Great question- I'm not sure. I haven't set up 2FA myself for a WP site before (traditional or headless), so I'm not sure at this point. Please let me know if you get that working, though! I'd love to check out your implementation!
How do you handle CSRF? By default when using cookie auth, WordPress adds a nonce to pages with forms and checks the nonce before returning data. Any cookie auth without a similar safeguard will be vulnerable to CSRF attacks.
Hey @bigj13310 - Great question! As you likely already know, in traditional, monolithic WordPress sites, GET requests are used to trigger data create/update/delete operations in many cases, so someone could trick a logged-in user into visiting a URL, which would perform an action they didn't intend to cause. If you build a headless app that does the same, then implementing nonces to prevent CSRF attacks would be a good idea.
In this example app, however, 100% of the GraphQL requests that modify data in WordPress are sent as POST requests, which are the result of a user performing some action, such as clicking a button. So a malicious actor tricking someone into simply visiting a URL (a GET request) would not result in any data mutations (which use POST requests) from being fired off. Further, that's not uncommon- standard practice with GraphQL is to send all requests as POST requests. That's what Apollo Client does by default for all requests, for example, unless you've explicitly configured it to do otherwise.
Please let me know if you can think of anything else related to CSRF attacks that I haven't considered, though! Always willing to learn more and up my security game. 👍
@@kellenmace3948 Even if the other (possibly malicious) URL was accessed via a GET request, couldn't scripts on that page still make malicious AJAX POST requests to your WordPress endpoints without you knowing? If the nonce is not required, then wouldn't these malicious requests be authorized by cookies alone (if sent with the POST request)?
@@andrewjamesdawes A malicious actor could trick a user into navigating to a certain route in this app, but not into clicking any buttons that trigger data create/update/delete operations via a POST request. The user would have to knowingly click those buttons themselves.
If there were rogue scripts running on the page (such as from a cross-site scripting (XSS) attack or a malicious browser extension), then anything is possible. Those rogue scripts could click buttons on the user's behalf on this website, or any other (their bank's website, their social media accounts, the admin dashboard of their WordPress site, etc.). That would be a much bigger security issue.
Hi, is it possible to run this as a chrome extension without disabling http only?
Hey Richard. I'm not sure what you mean. You shouldn't need a Chrome extension for this. I recommend having both the WP backend and the JS frontend served over https in production.
I am always getting viewers: null after logging in, anyone knows what the problem might be? I haven't touched anything from the repo aside from the graphql url..
Hey did you resolve it?
I cloned your project, cannot query GET_USER, as the viewer returns null, even after login. Can you please help me, i cannot comment on your blog article.
I want to use this authentication can i use it at nodejs server?? If yes how?? And how do i get the wordpress logged in user cookie? I want to use that cookie for other api's route at my node server?? Is it possible? Or any other idea please suggest its urgent..
Hi Kaur- Yes, you can set and get cookies in Node.js. This article shows how to access them in Express, for example: www.geeksforgeeks.org/how-to-access-http-cookie-in-node-js/.
Hi! Is it possible to check whether or not the user is logged in on server side? With getServerSideProps in nextjs?
yes, by forwarding the cookies from the request headers
@@bigj13310 How would I go about doing that?
@@Hackbandito This approach would add a lot of complexity to your app. You have to authenticate the user once on the initial page load from the Next.js server -> WP server, then authenticate them again from the browser -> WP server for all subsequent requests, and set up cookie forwarding. I would only do that if you really need SSR for some reason. For my apps, I prefer to do this:
Statically render all pages in Next.js (SSG). For any protected/gated content that the page may have, show a skeleton screen momentarily while a request is fired off to get the data from the WP backend. If the user is logged in and has the permissions necessary to view that content, then pop it into the UI. If the user was not logged in or did not have the permissions necessary to view that content, then programmatically redirect them to the Log In page, or show a "You don't have access to view this content" message.
Having the initial page load be SSG allows that page to be distributed across a CDN so user's get a very fast initial load (much faster than SSR) and gives you great Core Web Vitals scores. Authenticated users only have to see a skeleton/loading screen briefly before they see the content appears. And it also makes your authentication setup much simpler. Would that work for your app?
@@kellenmace3948 I see your point, but this would kinda defeat the main feature of NextJS, which is serverside rendered pages.
@@Hackbandito A few years ago when CSR and SSR were the only options, I would have agreed with you. Since Next.js now supports SSG and ISR however, I use those whenever possible to statically render the "app shell" (any content that's the same for all users) and distribute those static pages across a CDN, then fetch any gated content via a client-side request.
Another thing to think about is that even if you do SSR on the initial page load, you still have to fetch data from the client for any subsequent pages the user navigates to that contain gated content.
I'm certainly not trying to dissuade you from doing SSR, though! Just sharing how I approach things. If SSR is a better choice for your app for one reason or another, then go for it! 😊
Thank you for the great explanations! I have implemented the login and it works but it doesn't work on IOS mobile devices, any clues why? am trying to debug it but no solutions yet.
Have you found a solution for this?
hey if you can share your solutiuon... in safari doesn't work for me either. viewer returns null even if login is successfull
I just setup this new native cooke method locally on my machine and I have one issue. When I login with the frontend, everything works fine. When I login using my backend first, and try to refresh the frontend page, I end up with viewer: null. Any ideas? Thanks for the awesome walkthrough on everything else, learned a ton.
Hey Spencer! That is strange. I'm not sure offhand why you would be getting `null` for `viewer` if the user is logged in and the cookie is still valid. Were you able to get to the bottom of this issue? Thanks for watching! I'm glad the video was helpful! 👍
@@kellenmace3948 Thx! I ended up figuring it out, I wasn't passing the cookie along in the response so I kept getting null - oops! Lol Thanks again for this great tutorial!
@@spencerbigum1309 Hey did you resolve it?
@@HabitsAndRoutines I was able to get this working with Next Js, but I was not able to get it working with Remix if I recall. Remix would not read a secure cookie from another site on the server. It could only be done in the browser, which I think is why Next JS was able to do it.
@@spencerbigum1309 did you need extra package or something? How did it resolved.
From 13:50 you are making a point where you got some "top secret" content in your nextjs application. But shouldn't this information be fetched from an api with proper auth instead? Hardcoding secret info isn't a good way of doing things because people can just dig into the client side javascript code to figure out what the top secret content was....
In the case of "router guarding", the only guard you are creating is client sided.
Unless nextJS is actually fetching this info from something like getStaticProps, or getServerSideProps? Which is server side code for nextJS...
EDIT: I posted this before hearing 15:20....
I have used this authentication in reactjs its working fine i got the cookie ..now i want to send that cookie to the nodejs server in order to secure my nodejs server api's how do i send the cookie from reactjs??
In my nodejs server i am not getting the cookie.I have noticed that the cookie having key something like wordpress_logged_in_189675588665788 and its value is the one which provides the user info. Please let me know how to get it ASAP??