As part of the user authorization flow in GitHub, you can use the state parameter to send any random data plus the user id from your system that initiated the GitHub authorization flow. This state parameter should contain a random string to protect against forgery attacks and could contain any other arbitrary data (like your system user id), then when handling the authorization callback in your application, GitHub sends that state parameter back to your application. If the states don't match, the request was created by a third party and the process should be aborted.
You can read more about how GitHub OAuth Apps works here
Also, if you want to understand a little more about the state param as part of the OAuth 2.0 Authorization framework spec, read more about it here
In your backend, when you're handling the callback, you have now an access token returned from GitHub after the user authorization flow, with that, you can fetch the authorizing user information and associate the user id from GitHub with your system user id that you got from the state parameter, here is an example code of that:
import superagent from 'superagent';
// This code should be placed where you are handling the callback in your backend
// Put the access token from the callback response here
const access_token = '';
const userResponse = await superagent
.get('https://api.github.com/user')
.set('User-Agent', 'my-app')
.set('Authorization', `Bearer ${access_token}`)
.set('Accept', 'application/vnd.github.v3+json');
// Get the GitHub user id and connect it with the id you have in the state parameter
const gitHubUserId = userResponse.body.id;
The callback response body will have something like:
{
"access_token": "ghu_16C7e42F292c6912E7710c838347Ae178B4a",
"expires_in": 28800,
"refresh_token": "ghr_1B4a2e77838347a7E420ce178F2E7c6912E169246c34E1ccbF66C46812d16D5B1A9Dc86A1498",
"refresh_token_expires_in": 15811200,
"scope": "",
"token_type": "bearer"
}
The user response will be something like:
{
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false,
"name": "monalisa octocat",
"company": "GitHub",
"blog": "https://github.com/blog",
"location": "San Francisco",
"email": "octocat@github.com",
"hireable": false,
"bio": "There once was...",
"twitter_username": "monatheoctocat",
"public_repos": 2,
"public_gists": 1,
"followers": 20,
"following": 0,
"created_at": "2008-01-14T04:33:35Z",
"updated_at": "2008-01-14T04:33:35Z",
"private_gists": 81,
"total_private_repos": 100,
"owned_private_repos": 100,
"disk_usage": 10000,
"collaborators": 8,
"two_factor_authentication": true,
"plan": {
"name": "Medium",
"space": 400,
"private_repos": 20,
"collaborators": 0
}
}