Last active
January 17, 2024 21:07
-
-
Save jdembowski/403400d6093dc1d2e5fc846ea10ee375 to your computer and use it in GitHub Desktop.
Use cookie authentication to obtain a nonce for WP REST API calls that need authentication.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# This Python script will | |
# | |
# - Log into a WordPress installation using supplied credentials. | |
# - Get a single published post using the REST call /wp/v2/posts | |
# - Get a valid nonce from '/wp-admin/post.php?post=xxx&action=edit' | |
# - Use cookie+nonce to retrieve on post in draft status via REST. | |
# | |
# Getting that valid nonce must be performed prior to each WP REST call | |
# that uses cookie authentication and is not a read-only call. | |
# | |
# You need to have at least one published post and one in draft. | |
import getpass, requests, re, json | |
# WordPress URL and leave off the trailing '/' | |
wpurl=input('WordPress URL without the trailing slash: ') | |
username=input('Username: ') | |
password=getpass.getpass('Password for '+username+': ') | |
wp_login=wpurl+'/wp-login.php' | |
wp_redirect=wpurl+'/wp-admin/' | |
login_data={ | |
'log':username, 'pwd': password, 'wp-submit': 'Log In', | |
'redirect_to':wp_redirect | |
} | |
# Start session for preserving cookies. | |
wp_session=requests.session() | |
# Log into the WordPress site. | |
wp=wp_session.post( wp_login, data=login_data ) | |
wp=wp_session.get(wpurl) | |
# Get the REST endpoint less the trailing '/' from the 'Link' header. | |
wp_endpoint=re.sub('^\<','',wp.headers['Link']) | |
wp_endpoint=re.sub('\>.*$','',wp_endpoint)[:-1] | |
print('\nGetting the first returned post with \'publish\' status.') | |
print('This is an unauthenicated REST call for /wp/v2/posts (no nonce).\n') | |
# A REST call for posts that can be un-authenticated and needs no nonce. | |
# Give me a ping, Vasily. One ping only, please. | |
params={ | |
'status':'publish', | |
'per_page':'1', | |
'page':'1' | |
} | |
# REST call /wp/v2/posts | |
wp=wp_session.get(wp_endpoint+'/wp/v2/posts', params=params) | |
posts=wp.json() | |
print(json.dumps(posts, sort_keys=True, indent=4)) | |
print('\nGetting the first returned post with \'draft\' status.') | |
print('This needs an authenticated user (cookies) and a valid nonce.\n') | |
# Getting the posts in 'draft' status needs an authenticated user AND a valid nonce. | |
# To get one, we need to pull one out of the new post page so lets get one. | |
# Load the page for creating a new post. | |
# We're creating a new post we just need that page to fetch a valid nonce. | |
wp=wp_session.get(wpurl+'/wp-admin/post-new.php') | |
# Pull out of that page the nonce from | |
# 'var wpApiSettings = {"root":"wp-json\/","nonce":"xxxxxxxxxx","versionString":"wp\/v2\/"};' | |
wp_nonce=re.findall('var wpApiSettings = .*\;',wp.text) | |
wp_nonce=re.sub('^.*\"nonce\"\:\"','',wp_nonce[0]) | |
wp_nonce=re.sub('\".*$','',wp_nonce) | |
# Let's use that nonce in X-WP-Nonce headder | |
headers={ | |
'X-WP-Nonce':wp_nonce | |
} | |
# Give me a ping, Vasily. One ping only, please. | |
params={ | |
'status':'draft', | |
'per_page':'1', | |
'page':'1' | |
} | |
wp=wp_session.get(wp_endpoint+'/wp/v2/posts', params=params, headers=headers) | |
data=wp.json() | |
# Print that one returned draft post. You need a draft post for this to display anything | |
print(json.dumps(data, sort_keys=True, indent=4)) |
@jdembowski @oafx somehow y'all are still saving lives over here at the end of 2023.... thanks so much!!
In case anyone else finds this thread (hope you're on new WP and don't need this knowledge...), figured I'd note that I ran into a minor encoding issue when using @oafx 's endpoint which did make it much easier to obtain a nonce! The encoding issue could be related to my org's specific wordpress setup, but using request
library's built-in apparent_encoding
ended up with something like this which works for my need!
def getNonceHeadersFromSession(url, wp_session):
wp=wp_session.get(url+'wp-admin/admin-ajax.php?action=rest-nonce')
wp.encoding = wp.apparent_encoding
wp_nonce = wp.text.strip()
headers={
'X-WP-Nonce':wp_nonce
}
return headers
EDIT: typo
EDIT 2: also may need to .strip()
text from the admin-ajax.php endpoint—I've had two different sites respond differently to this call. Suspect it may be version related.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@oafx thanks so much!