Skip to content

Instantly share code, notes, and snippets.

@jdembowski
Last active January 17, 2024 21:07
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jdembowski/403400d6093dc1d2e5fc846ea10ee375 to your computer and use it in GitHub Desktop.
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.
#!/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))
@MaximeAntremont
Copy link

Thank you so much for this example ! It made me save a lot of time trying to import a lot of data on a wordpress website !

@jdembowski
Copy link
Author

I'm glad it helped you.

@oafx
Copy link

oafx commented Apr 20, 2022

Hi since I was searching for the same thing and stumbled about this gist I just wanted to note that I've found this /wp-admin/admin-ajax.php?action=rest-nonce (not sure if this is available by default) endpoint which makes it easier/more straightforward to obtain a nonce.

@mikemellor11
Copy link

@oafx what a legend

@rafaelmaeuer
Copy link

@oafx thanks so much!

@geofflambeth
Copy link

@jdembowski @oafx somehow y'all are still saving lives over here at the end of 2023.... thanks so much!!

@geofflambeth
Copy link

geofflambeth commented Dec 6, 2023

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