Skip to content

Instantly share code, notes, and snippets.

@ZipFile
Last active December 7, 2024 18:06
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 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.

@Zaczero
Copy link

Zaczero commented Apr 1, 2024

Yes this method is still usable. About the refresh token staying the same: this is how OAuth2 protocol works, what is returned is the new access_token (that expires in 1 hour). To make authenticated requests you need access_token, refresh_token is used to get that access_token every hour and it should not change (it's a long-term key). I suppose this instruction is slightly confusing python pixiv_auth.py refresh OLD_REFRESH_TOKEN, should be python pixiv_auth.py access_token REFRESH_TOKEN or something along these lines.

@GaryCraft
Copy link

GaryCraft commented Oct 12, 2024

is this method still reliable?
fanbox has made some changes in the last week and other methods don't

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