How do I Implement Bearer Tokens with Gin

Currently, I’m learning how to implement http authentication using bearer
tokens, but I don’t know where to start. I’ve looked guides online, but
I feel like I’m missing something. The way I currently do authentication is using gin for routing and bcrypt to store the hashed password in hex within postgresql where I verify the hash against the password provided by the user. Below is the example guides online present. Am I supposed to literally write out the user’s username and password for gin’s basic authentication. Also how is this supposed to work for a newly created
user that already listed? Its just not clear to me.

Online Implmenetation

package main

import (
    "crypto/rand"
    "encoding/hex"
    "github.com/gin-gonic/gin"
    "net/http"
    "strings"
)

var tokens []string

func main() {
    r := gin.Default()
    r.POST("/login", gin.BasicAuth(gin.Accounts{
        "admin": "secret",
    }), func(c *gin.Context) {
        token, _ := randomHex(20)
        tokens = append(tokens, token)

        c.JSON(http.StatusOK, gin.H{
            "token": token,
        })
    })
    r.GET("/resource", func(c *gin.Context) {
        bearerToken := c.Request.Header.Get("Authorization")
        reqToken := strings.Split(bearerToken, " ")[1]
        for _, token := range tokens {
            if token == reqToken {
                c.JSON(http.StatusOK, gin.H{
                    "data": "resource data",
                })
                return
            }
        }
        c.JSON(http.StatusUnauthorized, gin.H{
            "message": "unauthorized",
        })
    })
    r.Run() // Listen and serve on 0.0.0.0:8080 (for Windows "localhost:8080")
}

func randomHex(n int) (string, error) {
    bytes := make([]byte, n)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return hex.EncodeToString(bytes), nil
}

refs:

To be clarify , I’m asking how does the bearer token implementation
handle newly created users, if I’m supposed to write their login info within
the basic authentication middleware. For testing I’m using postman, but
I don’t how to use it with authentication and bearer tokens.

This has nothing to do with golang or gin. It is a very simple web working logic (if you don’t understand the working logic, you might as well think about it with the most primitive http interaction).
First is /login, which is the login interface. The user uploads the user password, and the server verifies the user password. When the verification is successful, a token value is returned. Generally speaking, the request end should not directly contact the native password, and often uploads the password hash.
Next is /resource, which is the resource interface. The user needs to provide the token obtained when logging in directly to prove the validity of the access. This token verification process is generally encapsulated into middleware. The token is generally in the header, and general tools have the function of filling in the header.
user → login → token
token → resource1
token → resource2
token → resource3

I understand that the user uploads their password so that it’s hash is compared
to the one stored in the database. What I’m having a hard time understanding is
how do I return a token value once the password has been verified as being correct?
Postgresql can’t store access tokens.

Ref:
Auth-Mthds for Postgresql

It depends on your design, for example you can put the token into the body of the http and then the front-end page to pull it out.

I’m currently trying to implement this in a small crud api project.
The instructions on how to run it are on the front page.

so to provide better context to my problem, my crudapi is about simulating a bikeshop.
I create an account by passing an account obj. to POST “localhost:8080/users”, which if successful adds the info to the usernames, usercontacts and passwords table respectively. usernames hold the user’s username, usercontacts stores their sensitive data(addres, fname, lname) and passwords stored their passwords as an encrypted hash. When an account is created the response {user_id: #, msg: registered } is sent back.

My current login route is a Post ‘localhost:8080/user/login’.
It hashes user provided pswd and checks its against
the currently hashed pswd stored in the database.
However, I want to return a token after authenticating.
Let me know if there’s anything missing.

JSON OBJECTS

Account
{
  "username": string,
  "fname": string,
  "lname": string,
  "address": string,
  "password": string
}
usernames
{ 
  "id": int,
  "user_id": int,
  "username": string,
}
usercontacts
{ 
  "id": int,
  "user_id": int,
  "fname": string,
  "lname": string,
  "address": string
}
passwords
{ 
  "id": int,
  "user_id": int,
  "password": string,
}
Login-Creds
{
  "username": string,
  "pswd": []byte
}

ROUTES

  • Create an account
    POST localhost:8080/users/ <account>

  • LoginIn
    POST localhost:8080/user/login <login-creds>

My Implmenetation

  • Routes > main.go
  • Account info & Authentication > accts.go
  • Field Error handling > fields.go

MAIN FILE

Create Account Route

func createAcct(r *gin.Engine) *gin.Engine {
	r.POST("/users/", func(c *gin.Context) {
		var acct accts.Account
		var acctErr fields.GrammarError
		var acctStatus *accts.Registered
		err := c.ShouldBind(&acct)
		if err != nil {
			acctErr.AddMsg(fields.BadRequest,
				"Binding Error: failed to bind fields to account object, mismatched data-types")
			c.JSON(fields.ErrorCode, acctErr)
			return
		}

		// validate account info
		acctStatus, acctErr = accts.AddAccount(&acct)

		// send response back
		errMsgSize := len(acctErr.ErrMsgs)
		switch {
		case errMsgSize > 0:
			c.JSON(fields.ErrorCode, acctErr)
		default:
			c.JSON(statusOK, *acctStatus)
		}

		//log.Println("Account: ", acct)
	})
	return r
}

Login Route

func logIn(r *gin.Engine) *gin.Engine {
	r.POST("/user/login", func(c *gin.Context) {
		var loginErr fields.GrammarError
		var rqstData respBodyData
		var userCred accts.LoginCred

		loginErr = rqstData.FieldErr
		err := c.ShouldBind(&userCred)
		if err != nil {
			loginErr.AddMsg(fields.BadRequest,
				"Binding Error: failed to bind fields to account object, mismatched data-types")
			c.JSON(fields.ErrorCode, loginErr)
			return
		}

		// validate account info
		authStatus, loginErr := accts.LogIntoAcct(userCred)
		if err != nil {
			loginErr.AddMsg(fields.BadRequest,
				"Binding Error: failed to bind fields to account object, mismatched data-types")
			c.JSON(fields.ErrorCode, loginErr)
			return
		}

		// send response back
		errMsgSize := len(loginErr.ErrMsgs)
		switch {
		case errMsgSize > 0:
			c.JSON(fields.ErrorCode, loginErr)
		default:
			c.JSON(statusOK, authStatus)
		}
	})
	return r
}

ACCTS FILE

Add Account

// adds the account info to the appropiate tables w/ the database
func AddAccount(acct *Account) (*Registered, fields.GrammarError) {
	acctErr := &fields.GrammarError{}
	validateAccount(acct, acctErr)

	if acctErr.ErrMsgs != nil {
		return nil, *acctErr
	}

	// if no errors add info to appropiate tables
	addUsername(acct, acctErr)
	if acctErr.ErrMsgs != nil {
		return nil, *acctErr
	}

	addUserContact(acct, acctErr)
	if acctErr.ErrMsgs != nil {
		return nil, *acctErr
	}

	// add passwords to table, don't if err existf
	addPassword(acct, acctErr)
	if acctErr.ErrMsgs != nil {
		// fmt.Printf("Errors in AddAccount Func, %v\n", acctErr.ErrMsgs)
		return nil, *acctErr
	}

	return &Registered{acct.ID, "registered"}, *acctErr
}

Login

// matches the client's username and pswd against the database
// if there's a match the user is logged in, otherwise
// there's an issue with username or password
func LogIntoAcct(userCred LoginCred) (*LoginStatus, fields.GrammarError) {
	// get the user struct check if it exists
	usrs, fieldErr := readUserByUsername(userCred.UserName)
	if fieldErr.ErrMsgs != nil {
		return nil, fieldErr
	}

	// get the stored pswd hash
	usr := usrs[0]
	pswds, fieldErr := ReadHashByID(usr.ID)
	if fieldErr.ErrMsgs != nil {
		return nil, fieldErr
	}

	// hash the given pswd and compare it to whats
	// stored in the databse
	hashedPswd := pswds[0].Password
	err := bcrypt.CompareHashAndPassword(hashedPswd, []byte(userCred.Password))

	if err != nil {
		// log.Printf("The Password Hash Comparison Failed: %v\n", err.Error())
		fieldErr.AddMsg(BadRequest, "Error: password is incorrect")
		return nil, fieldErr
	}

	return &LoginStatus{usr.ID, "LoggedIn"}, fieldErr
}

I don’t know what your question is.
If you are just wondering how to return the token, you only need to respond to the token value (such as body or header) when you log in.
Then carry the token in the next normal request (such as body, header, query)

I’m asking how do I create the token

Am I supposed to use postman to generate it for me or is the
database supposed to create it for me? I am asking this question
as a beginner who doesn’t understand how to implement token auth at all.

Your question is very strange, as if you don’t know what a token is.
Tokens are similar to cookies, both of which record status. As for what information a token carries, it is up to you to decide. At least it should be able to map to some information index.
For example, if the format is jwt, there may be:
sub (Subject): represents the user’s unique identifier.
iss (Issuer): represents the issuer of the token.
exp (Expiration): represents the expiration time of the token, in UNIX timestamp format.
iat (Issued At): represents the issuance time of the token.
aud (Audience): represents the recipient of the token.
hash (hash): represents the hash check value of the token.
Other custom fields (such as user roles, permissions, etc.).
These contents are designed by you, mainly depending on what your business needs, rather than asking others how to produce tokens.

1 Like

That helps, thank you