Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting back only code but no access token, or refresh token. #96

Open
javierohern opened this issue Jul 17, 2023 · 3 comments
Open

Getting back only code but no access token, or refresh token. #96

javierohern opened this issue Jul 17, 2023 · 3 comments

Comments

@javierohern
Copy link

javierohern commented Jul 17, 2023

Title.
Discord developer portal knows about my redirect_uri which is http://localhost:8000/callback

`discord = DiscordOAuthClient(
    "omitted client id",
    "omitted secret",
    "http://localhost:8000/callback",
    (
        "identify",
        "guilds",
        "email"
     )
)


@app.get("/login")
async def login():
    return {"url": discord.oauth_login_url}


@app.get("/callback")
async def callback(code: str):
    print(f"Received code: {code}")
    tokens = await discord.get_access_token(code)
    access_token = tokens[0]
    refresh_token = tokens[1]
    print(f"Access Token: {access_token}")
    print(f"Refresh Token: {refresh_token}")
    return {"access_token": access_token, "refresh_token": refresh_token}


@app.get(
    "/authenticated",
    dependencies=[Depends(discord.requires_authorization)],
    response_model=bool,
)
async def isAuthenticated(token: str = Depends(discord.get_token)):
    try:
        auth = await discord.isAuthenticated(token)
        return auth
    except Unauthorized:
        return False


@app.exception_handler(Unauthorized)
async def unauthorized_error_handler(_, __):
    return JSONResponse({"error": "Unauthorized"}, status_code=401)


@app.exception_handler(RateLimited)
async def rate_limit_error_handler(_, e: RateLimited):
    return JSONResponse(
        {"error": "RateLimited", "retry": e.retry_after, "message": e.message},
        status_code=429,
    )


@app.get("/user", dependencies=[Depends(discord.requires_authorization)], response_model=User)
async def get_user(user: User = Depends(discord.user)):
    return user


@app.get(
    "/guilds",
    dependencies=[Depends(discord.requires_authorization)],
    response_model=List[GuildPreview],
)
async def get_guilds(guilds: List = Depends(discord.guilds)):
    return guilds
`
@javierohern
Copy link
Author

javierohern commented Jul 17, 2023

I realized i left out
@app.on_event("startup")
async def on_startup():
await discord.init()
But after adding this I get
AttributeError: 'DiscordOAuthClient' object has no attribute 'init'

"EDIT"
I got the init problem to go away by installing dependencies from source. With that the
@app.on_event("startup") async def on_startup(): await discord.init()

The previous issue is now gone. But after logging in, I get the discord authorization page, and on login my url is appended with ?{code} but I'm still not receiving an access token?

Whole snippet of code ->

`@app.on_event("startup")
async def on_startup():
    await discord.init()


@app.get("/login")
async def login():
    return {"url": discord.oauth_login_url}


@app.get("/callback")
async def callback(code: str):
    token, refresh_token = await discord.get_access_token(code)
    return {"access_token": token, "refresh_token": refresh_token}


@app.get(
    "/authenticated",
    dependencies=[Depends(discord.requires_authorization)],
    response_model=bool,
)
async def isAuthenticated(token: str = Depends(discord.get_token)):
    try:
        auth = await discord.isAuthenticated(token)
        return auth
    except Unauthorized:
        return False


@app.exception_handler(Unauthorized)
async def unauthorized_error_handler(_, __):
    return JSONResponse({"error": "Unauthorized"}, status_code=401)


@app.exception_handler(RateLimited)
async def rate_limit_error_handler(_, e: RateLimited):
    return JSONResponse(
        {"error": "RateLimited", "retry": e.retry_after, "message": e.message},
        status_code=429,
    )


@app.get(
    "/user", dependencies=[Depends(discord.requires_authorization)], response_model=User
)
async def get_user(user: User = Depends(discord.user)):
    return user


@app.get(
    "/guilds",
    dependencies=[Depends(discord.requires_authorization)],
    response_model=List[GuildPreview],
)
async def get_guilds(guilds: List = Depends(discord.guilds)):
    return guilds
`

@bigwhitetuna
Copy link

bigwhitetuna commented Aug 23, 2023

@javierohern I was having a similar issue:

It's coming from this piece:

@app.get("/callback")
async def callback(code: str):
    token, refresh_token = await discord.get_access_token(code)
    return {"access_token": token, "refresh_token": refresh_token}

And more specifically, token, refresh_token = await discord.get_access_token(code).

It appears that there is something going wrong where the discord.get_access_token(code) is not returning a value, which is leaving "token" as None.

If you look at Discord's OAuth2 documentation, you can see how to call for an access token yourself.

Here is what I ended up doing to get around the packaged DiscordOAuthClient.get_access_token returning None:

### DISCORD AUTH ###
dotenv.load_dotenv()
### Identify env variables
DISCORD_CLIENT = os.getenv('DISCORD_CLIENT')
DISCORD_CLIENT_SECRET = os.getenv('DISCORD_CLIENT_SECRET')
DISCORD_REDIRECT_URI = os.getenv('DISCORD_REDIRECT_URI')
DISCORD_TOKEN_URL = "https://discord.com/api/v10/oauth2/token"

discord = DiscordOAuthClient(
    DISCORD_CLIENT, DISCORD_CLIENT_SECRET, DISCORD_REDIRECT_URI, ("identify", "guilds")
)

@app.get("/auth/callback")
async def callback(code: str, request: Request):
    import httpx
    async def exchange_code(code: str):
        payload = {
            "client_id": DISCORD_CLIENT,
            "client_secret": DISCORD_CLIENT_SECRET,
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": DISCORD_REDIRECT_URI,
            "scope": "identify guilds"
        }
        headers: dict = {"Content-Type": "application/x-www-form-urlencoded"}
    
        async with httpx.AsyncClient() as client:
            resp = await client.post(DISCORD_TOKEN_URL, data=payload, headers=headers)
        
        return resp.json()
                

    tokenData = await exchange_code(code)
    logging.info(f"Token data: {tokenData}")

This takes the code that returns with the auth, then uses it to request, and receive, a token. Note that tokenData is still in JSON format, you'll need to parse it as needed.

tokenData looks like this:

{
'token_type': 'Bearer', 
'access_token': stringfortoken', 
'expires_in': 604800, 
'refresh_token': 'd3mFnpt8IIjCmsPiaLO32qFJcdG1Y7', 
'scope': 'identify guilds'
}

@Tert0
Copy link
Owner

Tert0 commented Sep 29, 2023

I could not reproduce this issue with neither v0.2.4 nor v0.2.5.

It would be helpful if you could try to reproduce the following steps:

  1. Clone Repository and Checkout the tag v0.2.5.
git clone https://github.com/Tert0/fastapi-discord.git
cd fastapi-discord
git checkout v0.2.5
  1. Create a Discord Application and add http://127.0.0.1:8000/callback as a redirect URL.
  2. Insert your Client ID, Client Secret into examples/basic.py and use http://127.0.0.1:8000/callback as the redirect URL.
  3. Install the library and uvicorn
pip3 install fastapi-discord==0.2.5 uvicorn
  1. Run the API Server with
uvicorn examples.basic
  1. Get the OAuth URL with
curl http://localhost:8000/login

Or open http://localhost:8000/login in a browser. Then open the generated URL in a browser and click on authorize.

The browsers should display the access and refresh token in JSON.

{"access_token":"redacted","refresh_token":"redacted"}

I hope it works for you, at least it works for me.

(I have tested it with a docker container python (Debian-based))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants