Authentication process on golang web server

If you are starting to make some kind of web(or app) service, it is mandatory of thinking about authentication.

Every service has information to show, and it should divide public/private information to be displayed. If you are thinking of financial service, certification and security becomes even more important.

And now I’m starting to make some personal web project, and this is how I setup basic authentication logic.

React, and Golang

Every people who starts to implement web/app service, will think first of

which language/framework to use

You could just make it with something they’re most used to, if you are tending to finish project as soon as possible.

Or maybe you can challenge on using new stuffs which you’ve never used at work, because make something can be the quickest way to learn.

Well, this can cause delay on finishing project, so you can decide by which point you are focusing on.

Why I’ve select React for front-end is, it has been a year since I haven’t look into this(used Vue in current work), so I needed a motivation to refresh my knowledge on React. It is still most popular FE framework, and don’t wanted to slip my mind.

Golang…actually I don’t have any relationship with this. Didn’t have chance to use on work, and honestly, don’t like simplicity concept cause it seems this makes project implemented with Golang more complex.

(My knowledge on Golang is very poor, so this point of view can be changed…)

Though regardless how I think of this, it’s usage is growing exponentially globally, and simplicity of Golang helps user to make service with great performance fast, with short code.

So though there’s other handy programming languages, to try on golang, I’ve decided to bump up to this(it can be changed…).

from:https://dev.to/deepu105/my-reflections-on-golang-38jk

Basic process of authentication with JWT

Basic rule of authentication we can think easily, is to setup token data in header when requesting through API. And usually JWT is being widely used for generating this token.

JWT token is…

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. This 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…

From : https://jwt.io/introduction/

As you can see it includes various information, and it makes service developer can control expiration time, access scope of request with this.

JWT is composed with 3 feature, Header, Payload, and Signature.

‘Header’ contains informations which needs to verify then token. There are algorithm and type of token.

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

This will be encoded in Base64Url, and become first part of JWT.

‘Payload’

‘Payload’ contains statements about an entity and additional data(called ‘claims’ in document). It basically have information about

  • iss (issuer)
  • exp (expiration time)
  • sub (subject)
  • iat (issued at)

and several more public data like name, email…

1
2
3
4
5
{
  "sub": "1234567890",
  "iat": 1516239022,
  "name": "John Doe"
}

‘Signature’

‘Signature’ part is generated by signing encoded header, encoded payload, a secret with the algorithm specified in the header. For example:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

Result

Now, this is dummy token which has been generated with 3 part above:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

and as you can see, there are dot(.) character which works as separator of informations.

{HEADER}.{PAYLOAD}.{SIGNATURE}

Setup JWT middleware with golang + echo

There are several suggestions that ‘modern web framework’ does not need for web development with Golang. They said it is enough with using standard library, and several utilities(such as Gorilla).

But using framework still gives several convenience, by offering defined function which is widely used, and good documantation and reference.

And Echo,

Golang Echo

which I will use for web development, is one of popular web framework containing these advantages, with minimal size(I don’t prefer heavy full-framework).

Of course, there are defined middleware for JWT.

JWT middleware

You can setup JWT config by:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type jwtCustomClaims struct {
	Name  string `json:"name"`
	Admin bool   `json:"admin"`
	jwt.StandardClaims
}

// ...
config := middleware.JWTConfig{
	Claims:     &jwtCustomClaims{},
	SigningKey: []byte("secret"),
}
e.Use(middleware.JWTWithConfig(config))

In this example, it uses custom claim to extend information to be included in JWT. StandardClaims includes:

1
2
3
4
5
6
7
8
9
type StandardClaims struct {
	Audience  string `json:"aud,omitempty"`
	ExpiresAt int64  `json:"exp,omitempty"`
	Id        string `json:"jti,omitempty"`
	IssuedAt  int64  `json:"iat,omitempty"`
	Issuer    string `json:"iss,omitempty"`
	NotBefore int64  `json:"nbf,omitempty"`
	Subject   string `json:"sub,omitempty"`
}

so you can put personal data which is not here, on extended type.

This middleware will automatically send response as:

  • For valid token, it sets the user in context and calls next handler.
  • For invalid token, it sends ‘401 - Unauthorized’ response.
  • For missing or invalid Authorization header, it sends ‘400 - Bad Request’.

Now let’s see how to setup this on API router.

Setup authentication logic with Golang web server

Let’s assume that we are developing online shopping mall like amazon.

Amazon

Don’t mind of detail, and think of showing

  • My information
  • Items

in main page.

Process rule should be:

  • Items should be displayed whether user logged in or not
  • User information should be only displayed when user logged in
  • User could log in only when user are not logged in

and in short, responses should be:

API flow

Implementation

Echo also offers easy way to group APIs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type jwtCustomClaims struct {
	Name  string `json:"name"`
	jwt.StandardClaims
}

func main() {
  e := echo.New()

  // define apis to 'apiGroup' which doesn't require token
  apiGroup := e.Group("/api")
  apiGroup.POST("/login", Login)
  apiGroup.GET("/items", ...)
  
  // define apis to 'apiAuthGroup' which requires token
	apiAuthGroup := e.Group("/api/user")
	
	config := middleware.JWTConfig{
		Claims:     &jwtCustomClaims{},
		SigningKey: []byte("secret"),
	}
  
  apiAuthGroup.Use(middleware.JWTWithConfig(config))
  apiAuthGroup.GET("/user-profile", ...)

  // ...
  e.Logger.Fatal(e.Start(":1323"))
}

APIs which is defined in apiGroup(apis which starts with /api) will not be effected by JWT in header, but apiAuthGroup(apis which starts with /api/user) will only send ‘200 OK’ when request includes appropriate token in header.

Now it needs to return token data in /api/login, to let sender use after login succeeded.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type LoginParam struct {
	UserId string `json:"userId"`
	Password string `json:"password"`
}

func Login(c echo.Context) error {
	lp := new(LoginParam)
	if err := c.Bind(lp); err != nil {
		return echo.ErrUnauthorized
	}

	id := lp.Username
	pw := lp.Password

	if id != "user-id" || pw != "1234" {
		return echo.ErrUnauthorized
	}

	claims := &jwtCustomClaims{
		id,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 72).Unix(),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	t, err := token.SignedString([]byte("secret"))
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"token": t,
	})
}

Reference

updatedupdated2023-03-212023-03-21