You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/content/post/2026/02-07-github-login-fastapi-nextjs/index.mdx
+26-26Lines changed: 26 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,10 +1,10 @@
1
1
---
2
-
title: GitHub login with FastAPI and Next.js
2
+
title: Github login with FastAPI and Next.js
3
3
description: |
4
-
A practical example of implementing GitHub OAuth in FastAPI, and why Next.js server actions and API routes are convenient for managing cookies and domains.
4
+
A practical example of implementing Github OAuth in FastAPI, and why Next.js server actions and API routes are convenient for managing cookies and domains.
@@ -23,30 +23,30 @@ import OAuth2FlowDiagramImage from '../../../../content/post/2026/02-07-github-l
23
23
24
24
## Introduction
25
25
26
-
In this article, we will show how to implement GitHub login in a FastAPI and Next.js application. We use GitHub in this particular case, but the same approach applies to any OAuth provider, you only need to adjust the FastAPI redirect and callback endpoints. Since this is a Next.js app using server components, we will store the session in an HttpOnly cookie. We will dig into implementation details such as domains, cookies, redirects, and overall structuring to achieve a clean, maintainable, and robust solution.
26
+
In this article, we will show how to implement Github login in a FastAPI and Next.js application. We use Github in this particular case, but the same approach applies to any OAuth provider, you only need to adjust the FastAPI redirect and callback endpoints. Since this is a Next.js app using server components, we will store the session in an HttpOnly cookie. We will dig into implementation details such as domains, cookies, redirects, and overall structuring to achieve a clean, maintainable, and robust solution.
Let's begin with a quick reminder of how OAuth works in very simplified terms. OAuth is built around the principle of a trusted middleman: both we (our app) and the user know who GitHub is (the authorization server) and trust it. This means we can use GitHub to identify the user and obtain their information. For the user, this means GitHub vouches for our app's identity and legitimacy, clearly showing what information the app will access and at what level, so the user can give informed consent. Of course, there are many more implementation details, but this is enough for a high-level overview.
34
+
Let's begin with a quick reminder of how OAuth works in very simplified terms. OAuth is built around the principle of a trusted middleman: both we (our app) and the user know who Github is (the authorization server) and trust it. This means we can use Github to identify the user and obtain their information. For the user, this means Github vouches for our app's identity and legitimacy, clearly showing what information the app will access and at what level, so the user can give informed consent. Of course, there are many more implementation details, but this is enough for a high-level overview.
35
35
36
-
For our app, this practically means we need to register it with GitHub, obtain the app's client ID and client secret, and then, in the backend, use an OAuth client library to implement two endpoints:
36
+
For our app, this practically means we need to register it with Github, obtain the app's client ID and client secret, and then, in the backend, use an OAuth client library to implement two endpoints:
37
37
38
-
1. An endpoint that redirects the user to GitHub, where they can give consent.
39
-
2. A callback endpoint where GitHub redirects the user back to us, passing an authorization code that the auth library can exchange for an access token, which is then used to call GitHub APIs and obtain additional information about the user.
38
+
1. An endpoint that redirects the user to Github, where they can give consent.
39
+
2. A callback endpoint where Github redirects the user back to us, passing an authorization code that the auth library can exchange for an access token, which is then used to call Github APIs and obtain additional information about the user.
40
40
41
41
Additionally, within the callback endpoint we store the user's information (email, OAuth ID, name, avatar, etc.) in the database and use the autogenerated database user ID to generate a JWT access token, in the same way we do for a regular email/password authenticated user.
42
42
43
-
In this way, we achieve a unified interface for authenticating users, regardless of whether they log in with GitHub or via email/password.
43
+
In this way, we achieve a unified interface for authenticating users, regardless of whether they log in with Github or via email/password.
The one obvious and constant assumption is that we will use a FastAPI backend and a Next.js frontend. When it comes to authorization, this leaves additional room for deciding how we structure the logic and separate concerns. There is more than one way to do this, and some approaches can be fragile and hard to maintain, which is exactly what we want to avoid.
52
52
@@ -60,9 +60,9 @@ Let's go straight to the point and explain the optimal approach that we will use
60
60
61
61
- Cookies are tied to a domain and are impractical for passing arguments via HTTP responses. Cookies are meant for storing data, not for transmitting it. Consequently, for passing the `access_token` and `expires` values, we will use the response body (for server actions) and URL query parameters (for the OAuth redirect). Response bodies and URL query parameters are domain-independent and are designed for passing data between HTTP requests.
62
62
63
-
- Separation of concerns on the backend: FastAPI will contain all backend logic, including authorization. This means it will implement both OAuth endpoints (the redirect and callback endpoints). Next.js will handle cookie setting and unsetting logic: via server actions for email/password login, and via Next.js API routes for GitHub login. As a reminder, a server action is essentially a POST endpoint under the hood and can set or unset cookies.
63
+
- Separation of concerns on the backend: FastAPI will contain all backend logic, including authorization. This means it will implement both OAuth endpoints (the redirect and callback endpoints). Next.js will handle cookie setting and unsetting logic: via server actions for email/password login, and via Next.js API routes for Github login. As a reminder, a server action is essentially a POST endpoint under the hood and can set or unset cookies.
64
64
65
-
- The OAuth callback endpoint in FastAPI needs to initiate an uninterrupted redirect chain composed of two steps (FastAPI and Next.js API): `GitHub -> FastAPI callback redirect -> Next.js API redirect -> Next.js home page`. During this process, the `access_token` and `expires` values need to be passed as query parameters appended to the URL. Redirects are mandatory because the entire flow is driven by the browser, and we do not want the user's browser to just land on a raw API response, but rather on the website's home page as a successfully logged-in user.
65
+
- The OAuth callback endpoint in FastAPI needs to initiate an uninterrupted redirect chain composed of two steps (FastAPI and Next.js API): `Github -> FastAPI callback redirect -> Next.js API redirect -> Next.js home page`. During this process, the `access_token` and `expires` values need to be passed as query parameters appended to the URL. Redirects are mandatory because the entire flow is driven by the browser, and we do not want the user's browser to just land on a raw API response, but rather on the website's home page as a successfully logged-in user.
66
66
67
67
### Suboptimal approaches and their problems
68
68
@@ -82,11 +82,11 @@ There is some ambiguity caused by the redundancy of options, which can lead to s
82
82
83
83
That was a lot of text but still no code. On the other hand when we have clear mental model and worked out plan implementation is straight forward.
84
84
85
-
### Create OAuth app on GitHub
85
+
### Create OAuth app on Github
86
86
87
-
Like with any OAuth provider we need to register our app on GitHub and obtain client id and client secret. One GitHub specific is that you can have set only one redirect URL per app, so if you want multiple deployments you will need to create a separate app for each of them.
87
+
Like with any OAuth provider we need to register our app on Github and obtain client id and client secret. One Github specific is that you can have set only one redirect URL per app, so if you want multiple deployments you will need to create a separate app for each of them.
88
88
89
-
It's a straight forward process, go to your GitHub profile and open the following menus: `GitHub (top-right avatar) -> Settings -> Developer settings (bottom of the left sidebar) -> OAuth Apps -> New OAuth App`. Fill in your app info, including redirect URL where you should set the URL of your FastAPI callback endpoint, e.g. `https://api.my-website.com/api/v1/auth/github/callback`.
89
+
It's a straight forward process, go to your Github profile and open the following menus: `Github (top-right avatar) -> Settings -> Developer settings (bottom of the left sidebar) -> OAuth Apps -> New OAuth App`. Fill in your app info, including redirect URL where you should set the URL of your FastAPI callback endpoint, e.g. `https://api.my-website.com/api/v1/auth/github/callback`.
90
90
91
91
Then copy `Client ID` and `Client secret` and set inside the backend `.env` file.
92
92
@@ -140,13 +140,13 @@ oauth = create_oauth()
140
140
141
141
We can then use the instantiated OAuth client to implement the OAuth redirect and callback endpoints.
142
142
143
-
The redirect endpoint is quite simple, almost trivial. When the user hits this endpoint, they are redirected to the GitHub login page, where they can give consent. The `redirect_uri` variable contains the absolute URL of our callback endpoint, which we define next.
143
+
The redirect endpoint is quite simple, almost trivial. When the user hits this endpoint, they are redirected to the Github login page, where they can give consent. The `redirect_uri` variable contains the absolute URL of our callback endpoint, which we define next.
144
144
145
145
```py title="backend/app/api/routes/login.py"
146
146
@router.get("/login/github")
147
147
asyncdeflogin_github(request: Request):
148
148
"""
149
-
Redirect to GitHub login page
149
+
Redirect to Github login page
150
150
Must initiate OAuth flow from backend
151
151
"""
152
152
redirect_uri = request.url_for("auth_github_callback") # matches function name
Now we can define the callback endpoint, which is where GitHub sends the user after they have logged in on GitHub. This part is a bit more complex.
161
+
Now we can define the callback endpoint, which is where Github sends the user after they have logged in on Github. This part is a bit more complex.
162
162
163
-
GitHub includes an authorization code as a URL parameter, which we use to obtain an OAuth access token. We then use this token to call two separate GitHub APIs: one to retrieve the user’s profile information (full name, username, and OAuth ID), and another to retrieve the user's primary email address. Next, we find or create the user in our database. Finally, we use the user's database ID to create a JWT token, in exactly the same way as we do for a regular email/password user.
163
+
Github includes an authorization code as a URL parameter, which we use to obtain an OAuth access token. We then use this token to call two separate Github APIs: one to retrieve the user’s profile information (full name, username, and OAuth ID), and another to retrieve the user's primary email address. Next, we find or create the user in our database. Finally, we use the user's database ID to create a JWT token, in exactly the same way as we do for a regular email/password user.
164
164
165
165
Next, we calculate the `expires` value for the session cookie so that it matches the JWT `access_token` expiration. We then attach the `access_token` and `expires` values as query parameters to the redirect URL. The redirect URL is constructed as `f"{settings.SITE_URL}/api/auth/set-cookie"`, pointing to a Next.js API endpoint (which we define next) that is responsible for actually setting the cookie. Finally, we redirect the user.
Now that we have identified the user on GitHub and created the JWT `access_token`, the only remaining step is to set the cookie. As mentioned earlier, in the OAuth flow this is done in a Next.js API endpoint.
231
+
Now that we have identified the user on Github and created the JWT `access_token`, the only remaining step is to set the cookie. As mentioned earlier, in the OAuth flow this is done in a Next.js API endpoint.
232
232
233
233
Below is the complete endpoint implementation. As you can see, it is not too complicated. We simply parse the `access_token` and `expires` values from the URL query parameters, use them to construct the cookie, and attach the cookie to a redirect response that sends the user to the home page. This final step sets the cookie, and that's it.
So what is different between the email/password and OAuth flows?
347
347
348
-
The OAuth flow is based on two consecutive redirects: `FastAPI callback endpoint -> Next.js API set-cookie endpoint -> Home page`. This is mandatory because the callback endpoint is the only thing GitHub provides us, and the `access_token` and `expires` values must be passed as query parameters attached to the redirect responses.
348
+
The OAuth flow is based on two consecutive redirects: `FastAPI callback endpoint -> Next.js API set-cookie endpoint -> Home page`. This is mandatory because the callback endpoint is the only thing Github provides us, and the `access_token` and `expires` values must be passed as query parameters attached to the redirect responses.
349
349
350
350
In contrast, the email/password flow is based on an HTML form and a server action, which follows a standard request/response pattern. This allows the `access_token` and `expires` values to be sent directly in the response body.
351
351
@@ -384,7 +384,7 @@ Let's conclude this article by summarizing the upsides and downsides of choosing
384
384
- The frontend and backend are fully independent. We can use any domains for both by simply setting the `SITE_URL` and `API_URL` environment variables.
385
385
- We can deploy to platforms like `vercel.app` without needing any additional modifications.
386
386
- We maintain a single, unified interface for both email/password and OAuth logins.
387
-
- The approach is applicable to any OAuth provider, not just GitHub. You only need to define the appropriate redirect and callback endpoints in FastAPI.
387
+
- The approach is applicable to any OAuth provider, not just Github. You only need to define the appropriate redirect and callback endpoints in FastAPI.
0 commit comments