Skip to content

Instantly share code, notes, and snippets.

@ZipFile
Last active March 29, 2024 10:26
Show Gist options
  • Save ZipFile/c9ebedb224406f4f11845ab700124362 to your computer and use it in GitHub Desktop.
Save ZipFile/c9ebedb224406f4f11845ab700124362 to your computer and use it in GitHub Desktop.
Pixiv OAuth Flow

Retrieving Auth Token

  1. Run the command:

    python pixiv_auth.py login

    This will open the browser with Pixiv login page.

  2. Open dev console (F12) and switch to network tab.

  3. Enable persistent logging ("Preserve log").

  4. Type into the filter field: callback?

  5. Proceed with Pixiv login.

  6. After logging in you should see a blank page and request that looks like this: https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback?state=...&code=.... Copy value of the code param into the pixiv_auth.py's prompt and hit the Enter key.

If you did everything right and Pixiv did not change their auth flow, pair of auth_token and refresh_token should be displayed.

⚠️ code's lifetime is extremely short, so make sure to minimize delay between step 5 and 6. Otherwise, repeat everything starting step 1.

Refresh Tokens

python pixiv_auth.py refresh OLD_REFRESH_TOKEN
#!/usr/bin/env python
from argparse import ArgumentParser
from base64 import urlsafe_b64encode
from hashlib import sha256
from pprint import pprint
from secrets import token_urlsafe
from sys import exit
from urllib.parse import urlencode
from webbrowser import open as open_url
import requests
# Latest app version can be found using GET /v1/application-info/android
USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)"
REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback"
LOGIN_URL = "https://app-api.pixiv.net/web/v1/login"
AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token"
CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT"
CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"
def s256(data):
"""S256 transformation method."""
return urlsafe_b64encode(sha256(data).digest()).rstrip(b"=").decode("ascii")
def oauth_pkce(transform):
"""Proof Key for Code Exchange by OAuth Public Clients (RFC7636)."""
code_verifier = token_urlsafe(32)
code_challenge = transform(code_verifier.encode("ascii"))
return code_verifier, code_challenge
def print_auth_token_response(response):
data = response.json()
try:
access_token = data["access_token"]
refresh_token = data["refresh_token"]
except KeyError:
print("error:")
pprint(data)
exit(1)
print("access_token:", access_token)
print("refresh_token:", refresh_token)
print("expires_in:", data.get("expires_in", 0))
def login():
code_verifier, code_challenge = oauth_pkce(s256)
login_params = {
"code_challenge": code_challenge,
"code_challenge_method": "S256",
"client": "pixiv-android",
}
open_url(f"{LOGIN_URL}?{urlencode(login_params)}")
try:
code = input("code: ").strip()
except (EOFError, KeyboardInterrupt):
return
response = requests.post(
AUTH_TOKEN_URL,
data={
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code": code,
"code_verifier": code_verifier,
"grant_type": "authorization_code",
"include_policy": "true",
"redirect_uri": REDIRECT_URI,
},
headers={"User-Agent": USER_AGENT},
)
print_auth_token_response(response)
def refresh(refresh_token):
response = requests.post(
AUTH_TOKEN_URL,
data={
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"grant_type": "refresh_token",
"include_policy": "true",
"refresh_token": refresh_token,
},
headers={"User-Agent": USER_AGENT},
)
print_auth_token_response(response)
def main():
parser = ArgumentParser()
subparsers = parser.add_subparsers()
parser.set_defaults(func=lambda _: parser.print_usage())
login_parser = subparsers.add_parser("login")
login_parser.set_defaults(func=lambda _: login())
refresh_parser = subparsers.add_parser("refresh")
refresh_parser.add_argument("refresh_token")
refresh_parser.set_defaults(func=lambda ns: refresh(ns.refresh_token))
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()
@ZipFile
Copy link
Author

ZipFile commented Mar 9, 2021

@Cai-Zhenhui check your connection to pixiv. Maybe you need a proxy, as suggested by bakashigure above.

@railannad this message appears when your code is invalid, expired or was already used. Try again.

@railannad
Copy link

@Cai-Zhenhui check your connection to pixiv. Maybe you need a proxy, as suggested by bakashigure above.

@railannad this message appears when your code is invalid, expired or was already used. Try again.

I tried again and successed . Thank you very much

@koitoyuu19
Copy link

After I input Code and hit the enter key, nothing display. So I add proxy 127.0.0.1:7890,but it shows like this. Could you help me ? please.
7RC4%JQNV}0BZP VXU~D K3

@Thesola10
Copy link

To devs who wish to use this method to access the pixiv API: You can automate the login process for the end user!

@KrisTheNewest
Copy link

anyone knows what scale expires_in uses?

@ZipFile
Copy link
Author

ZipFile commented Jul 22, 2021

Seconds.

@KrisTheNewest
Copy link

so the key expires within an hour? talk about inconvenience, lol

@ZipFile
Copy link
Author

ZipFile commented Jul 22, 2021

Refresh can be automated, though.

@KrisTheNewest
Copy link

KrisTheNewest commented Jul 27, 2021

I tried porting/reverse engineering your py script to js and I get invalid_grant error.
image

@ZipFile
Copy link
Author

ZipFile commented Jul 28, 2021

You need to submit it as a form data. axios for nodejs does not support it out of the box.

@eggplants
Copy link

I have made this script more easier to run and available from another program. Also I have prepared a headless mode so it can work in environments without GUI.
Install: pip install gppt
See: https://github.com/eggplants/get-pixivpy-token

@enricocaliolo
Copy link

enricocaliolo commented Sep 1, 2021

I can't get my token. It always give me this same error. I already tried several times, but it doesn't work. I did what @upbit said, trying to get the code from the console. If I have to send some other things to clarify something, just let me know.

@ZipFile
Copy link
Author

ZipFile commented Sep 1, 2021

&via=login is not part of the code. Remove it.

@enricocaliolo
Copy link

enricocaliolo commented Sep 1, 2021

thank you, sir! Now that I have the access_token and refresh_token, what should I do to authenticate and start making requests? I tried a few things based on the documentation but I got a few errors:
image ( I tried this)

image ( and got these errors)

@DITlieD
Copy link

DITlieD commented Nov 4, 2021

enricocaliolo, did u find the solution ? im with the same error ;-;

@enricocaliolo
Copy link

enricocaliolo, did u find the solution ? im with the same error ;-;

unfortunately, no. I think either the wrapper library is broken or Pixiv doesn't allow this kind of authentication. What I did was learn BeautifulSoup and Selenium to scrape Pixiv, and it worked perfectly. Unless they update it and adress the error (which is in the issues), I think you are better off learning web scraping really

@BluerayDisc
Copy link

error:
{'error': 'invalid_request',
'errors': {'system': {'code': 918, 'message': '不正なOAuthクライアントです'}},
'has_error': True}
Can you tell me what's wrong with it?;-;

@eggplants
Copy link

eggplants commented Nov 9, 2021

@BluerayDisc
See forked gist of this pages by author of pixivpy: https://gist.github.com/upbit/6edda27cb1644e94183291109b8a5fde

@DITlieD
Copy link

DITlieD commented Nov 10, 2021

enricocaliolo, did u find the solution ? im with the same error ;-;

unfortunately, no. I think either the wrapper library is broken or Pixiv doesn't allow this kind of authentication. What I did was learn BeautifulSoup and Selenium to scrape Pixiv, and it worked perfectly. Unless they update it and adress the error (which is in the issues), I think you are better off learning web scraping really

Hmm, ok, thank you !

@540300286
Copy link

$ python pixiv_auth.py refresh OLD_REFRESH_TOKEN

error:
{'error': 'invalid_grant',
'errors': {'system': {'code': 1508, 'message': 'Invalid refresh token'}},
'has_error': True}

@RyouMon
Copy link

RyouMon commented Jan 20, 2022

This is a suggestion: set a timeout on requests.post() method.

@dnslin
Copy link

dnslin commented Jul 24, 2022

I appreciate the author's login script and the detailed readme, but I have a question whether CLIENT_ID and CLIENT_SECRET can be obtained by themselves to prevent CLIENT_SECRET from failing and getting the token.

@Kevin-2106
Copy link

Traceback (most recent call last):
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\connectionpool.py", line 703, in urlopen
httplib_response = self._make_request(
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\connectionpool.py", line 386, in _make_request
self._validate_conn(conn)
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\connectionpool.py", line 1042, in validate_conn
conn.connect()
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\connection.py", line 414, in connect
self.sock = ssl_wrap_socket(
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\util\ssl
.py", line 449, in ssl_wrap_socket
ssl_sock = ssl_wrap_socket_impl(
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\util\ssl
.py", line 493, in _ssl_wrap_socket_impl
return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
File "C:\Users\k2106\AppData\Local\Programs\Python\Python310\lib\ssl.py", line 512, in wrap_socket
return self.sslsocket_class._create(
File "C:\Users\k2106\AppData\Local\Programs\Python\Python310\lib\ssl.py", line 1070, in _create
self.do_handshake()
File "C:\Users\k2106\AppData\Local\Programs\Python\Python310\lib\ssl.py", line 1341, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\requests\adapters.py", line 489, in send
resp = conn.urlopen(
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\connectionpool.py", line 787, in urlopen
retries = retries.increment(
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\urllib3\util\retry.py", line 592, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='oauth.secure.pixiv.net', port=443): Max retries exceeded with url: /auth/token (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)')))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\Users\k2106\go-cqbot\HiBiAPI\pixiv_auth.py", line 158, in
main()
File "C:\Users\k2106\go-cqbot\HiBiAPI\pixiv_auth.py", line 154, in main
args.func(args)
File "C:\Users\k2106\go-cqbot\HiBiAPI\pixiv_auth.py", line 149, in
login_parser.set_defaults(func=lambda _: login())
File "C:\Users\k2106\go-cqbot\HiBiAPI\pixiv_auth.py", line 101, in login
response = requests.post(
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\requests\api.py", line 115, in post
return request("post", url, data=data, json=json, **kwargs)
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\requests\api.py", line 59, in request
return session.request(method=method, url=url, **kwargs)
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\requests\sessions.py", line 587, in request
resp = self.send(prep, **send_kwargs)
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\requests\sessions.py", line 701, in send
r = adapter.send(request, **kwargs)
File "C:\Users\k2106\go-cqbot\HiBiAPI\hibi\lib\site-packages\requests\adapters.py", line 563, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='oauth.secure.pixiv.net', port=443): Max retries exceeded with url: /auth/token (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997)')))

I've set up a pixiv-nginx, which works as a Reverse Proxy, when done, I can access pixiv in my browser and get the code through F12, but when I run the script it raised the error above. How can I fix the problem?
我设置了pixiv-nginx,原理是对pixiv进行反代,设置完之后我可以再浏览器里访问pixiv并且从F12里找到code,但是我运行这个脚本的时候出现了如上报错。请问我如何解决这个问题?
(The meaning of the English and Chinese should be the same.)

@ZipFile
Copy link
Author

ZipFile commented Jul 25, 2022

@dnslin Client secret is not a secret at all in this kind of auth flow. You can extract it with debugger/traffic analyzer.

@Kevin-2106 I do not know how pixiv-nginx works, better ask the devs whether it proxies private apis used by andoid/ios app. If it does, just update LOGIN_URL and AUTH_TOKEN_URL to point to your reverse proxy. Or alternatively, use regular proxy (see discussion above).

@dnslin
Copy link

dnslin commented Jul 25, 2022

@dnslin这种类型的流程,可以在身份鉴别器中使用。

@Kevin-2106如果是这样,立即更新并指向您的pixiv-nginx代理代理。或者,使用代理代理(见)。LOGIN_URL``AUTH_TOKEN_URL

Thank you very much

@Kevin-2106
Copy link

@Kevin-2106 I do not know how pixiv-nginx works, better ask the devs whether it proxies private apis used by andoid/ios app. If it does, just update LOGIN_URL and AUTH_TOKEN_URL to point to your reverse proxy. Or alternatively, use regular proxy (see discussion above).

Solved. I've installed a certificate and edited Hosts to access pixiv.net through 127.0.0.1 , while requests can't verify the certificate and raised an SSL Error.
To solve it, just add "verify=False" to requests.post(), then the code should be:
解决了。我为了直接访问pixiv改了hosts,安装了证书,但是requests不能验证我的证书于是抛出了SSL Error,解决方法是在requests.post()中增加"verify=False"。改后代码如下。

response = requests.post(
        AUTH_TOKEN_URL,
        data={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "code": code,
            "code_verifier": code_verifier,
            "grant_type": "authorization_code",
            "include_policy": "true",
            "redirect_uri": REDIRECT_URI,
        },
        headers={
            "user-agent": USER_AGENT,
            "app-os-version": "14.6",
            "app-os": "ios",
        },
        verify=False,
        **REQUESTS_KWARGS
    )

Thanks anyway!

@EnderAvaritia
Copy link

i think i full the token truely
image
but it can't work
image

@GullfossLK
Copy link

I tried to refresh my refresh token but the same token was returned. How can I refresh it?
タイトルなし

@kaoshawn04
Copy link

Is this method still usable?

@SakiSakiSakiSakiSaki
Copy link

@GullfossLK It keeps returning the same refresh token for me as well. Not sure what to do now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment