---
title: "How to use JWT to secure your GitHub OAuth callback endpoint"
description:
  "Sending data to OAuth servers using JWT enables applications to verify the
  authenticity of the data that is sent back to the callback endpoint."
canonical_url: "https://www.bigbinary.com/blog/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint"
markdown_url: "https://www.bigbinary.com/blog/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint.md"
---

# How to use JWT to secure your GitHub OAuth callback endpoint

Sending data to OAuth servers using JWT enables applications to verify the
authenticity of the data that is sent back to the callback endpoint.

- Author: Jagannath Bhat
- Published: March 7, 2023
- Categories: Misc

JSON Web Tokens (JWTs) have become a popular way to manage user authentication
and authorization. In this blog, we will explore how to use JWTs in GitHub OAuth
process, including how to encode additional parameters in the JWT to improve the
security and functionality of your GitHub OAuth integration. Let's start by
looking into what OAuth is.

## OAuth 2.0

OAuth 2.0 is an authorization framework that enables an application to access
data from a server on behalf of a user. For example, OAuth enables applications
to access data from Google, Facebook, GitHub, etc., on behalf of users having
accounts in that service.

Let's say you have an application that requires access to a user's data on
GitHub. The following are the steps involved in authorizing your application
with GitHub:

1. The application requests authorization from GitHub. The following are some of
   the parameters this request should contain:

   - `client_id` - This is an ID used by GitHub for identifying the application.
     You need to register your application with GitHub to get this ID.
   - `redirect_uri` - The URI to which GitHub should send back a request once
     the authorization is approved.
   - `login` - This is a username on GitHub. The application requires access to
     data on behalf of the user with this username.
   - `state` - This should ideally be a string of random characters. Store this
     string in memory because it will be used in another step. This parameter is
     optional but highly recommended. We'll look into why later.

2. GitHub then asks the user to grant the authorization. The user might be
   prompted to log in to GitHub if they have not already logged in. Then GitHub
   displays information on the application and lists all the data the
   application wants to access. If the user denies authorization, the process
   ends here. If the user approves, we move on to the next step.

3. GitHub sends a request to the application through the URI passed as
   `redirect_uri` in the first step. This request will contain a `code`
   parameter that serves as a temporary authorization code and a `state`
   parameter.

4. The OAuth process must be dropped immediately if the value of the `state`
   parameter from the previous step does not match the random string stored in
   memory in step 1. This ensures that the OAuth process was initiated by your
   application. We'll look more into this later.

5. The application should send another request to GitHub to generate a permanent
   authentication token. This request should contain the `code` sent by GitHub
   in step 3.

6. GitHub responds with an authentication token that can be used by the
   application to access data on behalf of the user.

![GitHub OAuth process](https://www.bigbinary.com/blog/images/images_used_in_blog/2023/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint/github_oauth_process.png)

## The state parameter

The `state` parameter plays a crucial role in ensuring that the GitHub OAuth
process was initiated by your application or a trusted source. An unauthorized
third party could pose as your application by using your `client_id`. The value
of `client_id` is not exactly a secret. GitHub treats any OAuth process
initiated using your ID as an OAuth process started by your application.

Let's see how the OAuth process would play out when initiated by an unauthorized
third party:

1. Third-party requests authorization from GitHub. The third party would pass
   your ID as `client_id`. This would lead GitHub to believe the request is from
   your application. The third party may or may not pass a `state` parameter.

2. GitHub then asks the user to grant the authorization. If the third party uses
   one of their own accounts, they could grant the authorization. The third
   party could also convince a user to grant permission using social
   engineering. For example, they could perform a phishing attack using an email
   designed to trick the user into believing that the email was from your
   application.

3. GitHub sends a request to the application through the URI passed as
   `redirect_uri` in the first step. This request would contain the `code`
   parameter, and the `state` parameter if it was passed in step 1.

4. It would be clear that the process was not initiated by your application if
   the `state` parameter is missing. Even if there was a `state` parameter, it
   would not be found in memory. This is because the state parameter was
   generated by a third party and not your application. Only those state
   parameters generated by your application will be found in your memory.

## JSON Web Tokens

JSON Web Tokens (JWT) is a structured format that contains header, payload, and
signature components. Let's take a look at the significance of these components:

1. **Header** - The header is a JSON string that typically contains the signing
   algorithm used, such as HMAC, SHA256, or RSA.

2. **Payload** - The payload is a JSON string that contains claims and
   additional data. Claims can be used to enforce security constraints and
   validate the authenticity of the token and its contents. For example, a
   claims can specify the token expiration time. The expiration time, most
   commonly represented by `exp`, represents the date and time after which the
   token will no longer be considered valid.

3. **Signature** - The signature is used to verify that the sender of the JWT is
   who it says it is and to ensure that the token has not been tampered with
   along the way. Just like the hand signature of a person, it is hard to forge
   a JWT signature. (Digital signatures are significantly harder to forge
   compared to a person's hand signature)

### Generating a JWT

The following steps outline the process of generating a JWT:

1. The header and the payload of the JWT are first encoded using Base64
   encoding. So now we have two strings - the encoded header and the encoded
   payload.

2. The encoded header and the encoded payload are concatenated into a single
   string, with dots (.) separating each part. The concatenated string is signed
   using the signing algorithm specified in the header. The signing algorithm
   uses a secret key, that is known only to the issuer (your application), which
   ensures that only the issuer can generate a valid signature. The result of
   the signing process is a signature, which is also a string.

3. The encoded header, the encoded payload, and the signature are concatenated
   into a single string, with dots (.) separating each part. The resulting
   string is the JWT, which can be transferred securely between parties.

For example, let's say we have the following header:

```json
{ "alg": "RS256" }
```

The value of `alg` contains the algorithm used for signing the JWT. Also, let's
say we have the following payload:

```json
{ "username": "sam@example.com", "exp": 1676263763 }
```

The `username` is data that needs to be transferred. `exp` contains the
expiration time claim of the JWT.

When both these components are encoded, we get:

- Header - "eyJhbGciOiJSUzI1NiJ9"
- Payload - "eyJ1c2VyIjoic2FtQGV4YW1wbGUuY29tIiwiZXhwIjoxNjc2MjYzNzYzfQ"

When these are signed using the RS256 algorithm and a secret key, a signature is
produced. The following signature was generated using the RS256 algorithm and a
secret key (which will remain secret):

```text
NlAT6awp68dCEcFXbDeeLTzZekqUmB3f6kr3jkGSFmrKa5zvLmFGeraWba_fUuQLVhRtcXUPZbRR1DKnKH0HVf1rRDvOqezwbhe-hR1wlz6vZkHuPjtYSCLx_aybGm7dy2ijfTQwYd14cD9ZiMI5vf6XcDDfE7mkhu0ogCOnqR1v3KOEWJkMkvGBHfHKuf9FKYbWltHtUE6bAEO1orq0JayD8UNUKxdGkElXA7mkuIEexmBuieG9PJ2ow_uo05QCsqDvxlzOCMMIe7WdT7gmz4myiZ7lVuUcL1V2-Y1PJqWDyqDZbKNxd4X_CwW0RLOF1pw9S2URgybqHZFG0murNw
```

So the final JWT would be:

![Screenshot of decoded JWT](https://www.bigbinary.com/blog/images/images_used_in_blog/2023/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint/jwt_decoded.png)

The image above was captured from the decoding tool in
[jwt.io](https://jwt.io/).

JWT is basically a Base64 encoded string with a signature attached to it. Having
that signature component makes JWT a secure format for transferring data. It is
possible to simply encode data with Base64 and transfer that string. From the
example above, transferring the encoded payload
"eyJ1c2VyIjoic2FtQGV4YW1wbGUuY29tIiwiZXhwIjoxNjc2MjYzNzYzfQ" can also get the
data to the other party. However, the party that receives the data have no way
of ensuring that the data was sent by a trusted source and that the data was not
tampered with along the way.

### Verifying JWT tokens

Anyone who has the secret key used to sign a JWT, can verify the integrity of
the JWT. The steps involved in verifying the signature of a JSON Web Token (JWT)
are:

1. Split the JWT into the encoded header, the encoded payload and the signature,
   using the dot (.) used to separate the three components.

2. Decode the encoded header and the encoded payload using Base64 to get the
   header and payload JSON strings.

3. The application recreates the signature by signing the encoded header and the
   encoded payload using the signing algorithm in the header and the secret key.
   If the signature created in this step is the same as the signature in the
   JWT, the JWT is valid.

4. The application validates the claims in the payload if there are any.

It can be verified that the JWT was generated by your application and has not
been tampered with, if the signature is valid. If the JWT header or payload was
tampered with, the signature produced while verifying would be different from
the one in the JWT.

## Using JWT for the state parameter

We can generate a JWT token and pass that as the state parameter in the GitHub
OAuth authorizing process. Here's how the process will be different when using
JWT:

1. The application requests authorization from GitHub. This time the `state`
   parameter will be a JWT signed using a secure algorithm and a secret key. The
   JWT need not be stored in memory.

2. GitHub gets the approval of the user.

3. GitHub sends a request to the application through the URI passed as
   `redirect_uri` in the first step. This request will contain the `code` and
   the `state` parameters. Here, the state parameter would be a JWT.

4. Validate the JWT from the `state` parameter. If the JWT is invalid, drop the
   authorization process immediately.

## Advantages of using JWT for the state parameter

1. **No Storage requirement** - When using a random string for the state
   parameter, that string has to be stored, so that it can be used later for
   verification. However, JWTs can be verified without storing them once they
   are generated.

2. **Ability to send additional data** - The payload component of JWT can be
   used to send additional data such as user data, permissions data, etc.

3. **Security** - Using JWT can ensure that the OAuth process was initiated by
   your application or a trusted source. In addition, JWT also ensures the
   integrity of the data. This means that we can ensure that the payload in the
   JWT has not been tampered with.

4. **Flexibility** - The claims in the payload component of JWT can be used
   further enhance the security of the JWT by implementing custom security
   checks based on the claims in the payload.

5. **Standardization** - JWT is a widely used format for transferring data.
   Using JWT would be beneficial when there are different applications and
   services involved in the OAuth process.

## References

1. OAuth 2.0 -
   <https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2>

2. GitHub OAuth Authorization process -
   <https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps>

3. Phishing Attack -
   <https://www.imperva.com/learn/application-security/phishing-attack-scam/>

## Links

- [Human page](https://www.bigbinary.com/blog/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint)
