Secure Your JWT Token in Gin with RS256 Sign Method

TarrantRo
Stackademic
Published in
4 min readOct 8, 2023

--

What is JSON Web Token?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. For more information: https://jwt.io/introduction

The authentication information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.

JSON Web Tokens consist of three parts separated by dots (.), which are:

  • Header
  • Payload
  • Signature

The sign algorithm is in the header. For example if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

Why not choose HS256 to sign your JWTs?

First thing first, Signing JWTs doesn’t make their data unreadable. Signatures only verify that the content of the JWT was not changed. The content itself encode with base64 format, and we shouldn’t put any private information into the JWTs. It indicates that the signing method is only to ensure the integrity of the token.

RS256 and HS256 are the most common algorithms used for signing JWTs.

HS256 Signing Algorithm

HS256 (HMAC with SHA-256) is a symmetric keyed hashing algorithm that uses shared secret key. The key is used for both generating the signature and validating it.

RS256 Signing Algorithm

RS256 (RSA Signature with SHA-256) is an asymmetric algorithm that uses a public/private key pair. The identity provider uses a private key to generate the signature. and the receiver uses a public key to validate the JWT signature. The public key used to verify and the private key used to sign the token are generated as a pair.

For more information, can refer to below documents:

While both HS256 and RS256 can be used to verify the integrity of JWTs, the recommended algorithm at this time is RS256. That’s because:

  • HS256 is a symmetric algorithm, the provider need to distribute the keys between client and authenticate service.
  • And the same key is used to sign a JWT and verify that signature, if the key exposed to 3rd parities by client end, then 3rd parities can forge the signature.
  • Even the performance of HS256 is better than RS256. But with RS256 algorithm, we can use a private key to sign token only and distribute the public keys to the client. The public key is only for validation so 3rd parities can’t forge the signature even the key is exposed.

How to use RS256 to sign the JWTs?

We just had a brief introduction of RS256 and HS256 algorithms. If a user want to use RS256 to sign the JWTs, how should we do?

Let’s see how it works in Gin Web Framework. I will use middleware to generate and authenticate the JWTs, here is the code gists

main.go
auth.go

The code will run a Web services to sign JWTs when user provide auth information in /token API and user can use that token to access /welcome API and return the auth information. How it works, let’s go through it steps by steps.

  1. Generate RSA key pair. You will use that key pair to sign/validate the token.
$ openssl genrsa -out /opt/fizz/private_key.pem 2048
$ openssl rsa -in /opt/fizz/private_key.pem -pubout -out /opt/fizz/public_key.pem -RSAPublicKey_out

2. Download the gists to local, save to main.go and auth.go , start the service.

$ go mod init main
$ go mod tidy
$ go run main.go auth.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST /token --> main.main.func1 (6 handlers)
[GIN-debug] GET /welcome --> main.main.func2 (7 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

3. Now, access /welcome API, you will find the service return 401 code

$ curl http://127.0.0.1:8080/welcome -v
> GET http://127.0.0.1:8080/welcome HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.78.0
> Accept: */*
> Proxy-Connection: Keep-Alive
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Connection: keep-alive
< Date: Sun, 08 Oct 2023 07:49:38 GMT
< Keep-Alive: timeout=4
< Proxy-Connection: keep-alive
< Content-Length: 0
<
* Connection #0 to host 127.0.0.1 left intact

4. Let’s login the service in /token API, it will return the token by default. The code is only for demo, you can have your user login logic inside.

$ curl -X POST -d "{\"username\": \"testuser\", \"password\": \"testpassword\"}" http://127.0.0.1:8080/token
{"token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoX2luZm8iOnsidXNlcm5hbWUiOiJ0ZXN0dXNlciIsInBhc3N3b3JkIjoidGVzdHBhc3N3b3JkIn0sImlzcyI6ImF1dGgucmFuZG9tY2xvdWQxMjMuY29tIiwic3ViIjoidGVzdHVzZXIiLCJhdWQiOlsic3RzLnJhbmRvbWNsb3VkMTIzLmNvbSJdLCJleHAiOjE2OTY3NTg4NDYsIm5iZiI6MTY5Njc1MTY0NiwiaWF0IjoxNjk2NzUxNjQ2LCJqdGkiOiJkMmY2MGEwMS00NjY1LTQzOTktYTQxNi1iYzBmZTg5YTg3YmQifQ.RT5GnrnckJT0EGLD_eJv7GQs6_otRPKg3llsQSTmifcOgNW0ZoFktKV-u1224A0sOaoD9JXu5gbSXVq7eVSIZCcu1hXy4ZpmaTefATDUOIlUwk0UWbSAvZoSf8_DFpziBBvrD5ehIzFWfZ_gq89gG8GMG9K_5Lw0cGyw_sFQ3VsY9sImlhMVKmuiqjDTwuoQ3JGRXTSY5UOaU4K7nXSFzUG-kfMjROge09yGuqKPlBnoI2rWgXwXHAShgpXKXWjlDHFP9-V1F2ueCqQTTiRm7zy4dLNsreTgRADFCn_JHLncY9Ye8kC1WQHggVrBu_T1L_7hbrC-M-z4DCH3J6XwdQ"}

you can go to jwt.io to validate the token and check how the JWT structed.

5. You can use this token to access /welcome API now.

 $ curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoX2luZm8iOnsidXNlcm5hbWUiOiJ0ZXN0dXNlciIsInBhc3N3b3JkIjoidGVzdHBhc3N3b3JkIn0sImlzcyI6ImF1dGgucmFuZG9tY2xvdWQxMjMuY29tIiwic3ViIjoidGVzdHVzZXIiLCJhdWQiOlsic3RzLnJhbmRvbWNsb3VkMTIzLmNvbSJdLCJleHAiOjE2OTY3NTg4NDYsIm5iZiI6MTY5Njc1MTY0NiwiaWF0IjoxNjk2NzUxNjQ2LCJqdGkiOiJkMmY2MGEwMS00NjY1LTQzOTktYTQxNi1iYzBmZTg5YTg3YmQifQ.RT5GnrnckJT0EGLD_eJv7GQs6_otRPKg3llsQSTmifcOgNW0ZoFktKV-u1224A0sOaoD9JXu5gbSXVq7eVSIZCcu1hXy4ZpmaTefATDUOIlUwk0UWbSAvZoSf8_DFpziBBvrD5ehIzFWfZ_gq89gG8GMG9K_5Lw0cGyw_sFQ3VsY9sImlhMVKmuiqjDTwuoQ3JGRXTSY5UOaU4K7nXSFzUG-kfMjROge09yGuqKPlBnoI2rWgXwXHAShgpXKXWjlDHFP9-V1F2ueCqQTTiRm7zy4dLNsreTgRADFCn_JHLncY9Ye8kC1WQHggVrBu_T1L_7hbrC-M-z4DCH3J6XwdQ" http://127.0.0.1:8080/welcome
{"message":"welcome testuser"}

Since now, we can sign the JWT token with RS256 algorithm. If you like the article, please don’t hesitate to clap and follow. Thanks!

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.

--

--

IT guy who love movies, Japanese manga. Have some experiences in Linux system, container/k8s, devops, cloud, etc.