goethe

package module
v0.0.0-...-ba7e2e6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2023 License: MIT Imports: 8 Imported by: 0

README

JWT token Secured Gin

Simplifies the creation of a JWT token secured API using Gin and GORM.

Simply provide Callbacks for verifying name-key pair and use Renette-api to keep the token and access the endpoints created with Gin.

How to use

Implement Callbacks
  • I want as least dependencies as possible and to customize, you need to implement Callbacks for verifying service name and key and claim with your database or whatever you want to use.

  • Implement your own model for API Credentials and use it to verify in callbacks.

  • Implement Callbacks and pass it to goethe.entry(&Callbacks)

    • OnVerifyCredentials - verify service name and key, return AUTHLEVEL (example
    • OnVerifyClaims - verify claims, return GClaims and error
var Callbacks type gCallbacks struct {
	OnVerifyCredentials func(name, key []byte) int
	OnVerifyClaims      func(c *gin.Context, claims jwt.MapClaims) (*GClaims, error)
} = {
    // verify service name and key, return AUTHLEVEL
    OnVerifyCredentials func(name, key []byte) (int) // AUTHLEVEL
    OnVerifyClaims      func(c *gin.Context, claims jwt.MapClaims) (*GClaims, error)
}
  • Authlevel Describes basic authentication level, you can use for checking authLevel > AUTH_SERVICE_NAME to check session is valid authenticated using service name and key or even user login (should implement your own user login system)

It's defined as:

type GOETHE_AUTH_LEVEL uint8

const (
	AUTH_NONE              GOETHE_AUTH_LEVEL = iota // none authentication
	AUTH_SERVICE_NAME                               // valid service name provided
	AUTH_SERVICE_WRONG_KEY                          // service name and key, but key wrong
	AUTH_SERVICE_KEY                                // both service name and key were valid
	AUTH_USER_LOGGED_IN                             // user is logged in
)
  • JWT Claims Change the claims whenever you need to store something in the token, eg. user login sesion ID, OR if you need to reissue the token with the next response.
// if you save anything to "user" claim, AUTHLEVEL will be AUTH_USER_LOGGED_IN next time so you can check it
ginContext.MustGet("JWT").(*GClaims).SetClaims("user", 21)

Initialize *Gin.engine with goethe
// create callbacks and pass it to goethe
goethe.Callbacks = &goethe.Callbacks = &goethe.GCallbacks{
    // issueJWT with custom claims
    BeforeIssueJWT: utils.BeforeIssueJWT,
    // verify Service Name and Key
    OnVerifyCredentials: utils.OnVerifyAPICredentials,
    // verify custom JWT claims (e.g. API session)
    OnVerifyClaims: utils.OnVerifyCustomClaims,
    // ...
}

// create gin engine
engine := goethe.GoetheEntry()
User login

To use AuthLevel.AUTH_USER_LOGGED_IN simply add user object (after authentication) to the response using GClaims.SetClaims("user", &User{...}) and then use GClaims.NewResponse(...) to create a response with new token and user object in it.

Register GIN groups/endpoints
  • Use goethe.RequireAuthLevel HOF with AUTH_LEVEL to create handler that protects the endpoint (or group of endpoints) by limiting minimal AUTH_LEVEL

  • Respond with goethe.Response using context.c.MustGet("JWT").(*GClaims).NewResponse(...) to return a JSON in a shape for usage with Renette-api

For example, this needs client (app) to provide a valid service name and service key in request header (Renette-api does this for you :))

POST("/endpoint", func(c *gin.Context) {
    // use goethe.NewResponse(...) for API response, JWT will be automaticly re-generated if claims changed (eg. client authenticated by service name-key pair or user logged or any claims changes)
        G := c.MustGet("JWT").(*GClaims)

        if (userAuthenticadedSuccessfully){
            // if claim changes, the response will automatically contain new token and "accessKey" action for Renette to update token
            G.SetClaims("user_session_id", 21)
        }
        c.JSON(http.StatusOK, G.NewResponse(
            // action executed on client after receiving data
			[]string{"message"},
            // data to be sent to client
			map[string]string{
				"message": "What's the answer to the ultimate question of the life, the universe, and everything?",
                "theAnswer": 42
			},
            // internal integer code for the response (not HTTP) that client can better determine what to do
			0,
		))
})
// add middleware to protect endpoint
appGroup := engine.Group("/privateResource", goethe.RequireAuthLevel(goethe.AUTH_SERVICE_KEY))
{
    appGroup.GET("/action", func (c *gin.Context) {
        // the middleware makes sure the client had used service name and key to authorize
    })
}
Generate credentials for client
  • Use goethe.GenerateCredentials to generate a service name and service key pair for first time, you can run it in init function, but must provide working and synced GORM database connection
CreateCredentials(DB, time.Hour * 3)
Start the server
go run main.go
Use Renette-api to access the endpoints

Install yarn add renette-api and initialize it with service name and key pair or just name (if not needed for the endpoint)

// create a new instance of Renette
import CAPI from "renette-api";

// this is not your key, it's just an example
const API = new CAPI({
    name: "fa195fc89d6df5b7eb336b92da42cfa4",
    key: "8b386470e7a67007dcd8f34925cc61552bceb8c76052b6f838684c0f66e9d516",
});

export default API;
  • Alternatively, you can set X-Service-Name and X-Service-Key headers in your requests to Chk/init and then provide received accessKey on every request in X-Access-Key header

Donate

To keep me motivated to work taking care of this code, please donate me.

One time or regularly, I appreciate the support :) Donate via Paypal

Enjoy!

Documentation

Index

Constants

View Source
const (
	ENDPOINT_NAME       = "Chk"
	HEADER_SERVICE_NAME = "X-SERVICE-NAME"
	HEADER_SERVICE_KEY  = "X-SERVICE-KEY"
	HEADER_ACCESS_KEY   = "X-ACCESS-KEY"
)
View Source
const TokenValidity = time.Hour * 3

Variables

This section is empty.

Functions

func Authorize

func Authorize(c *gin.Context)

Authorize service with key if JWT was already issued, reissues the token, assigns authLevel to context

func GoetheEntry

func GoetheEntry() *gin.Engine

func Init

func Init(c *gin.Context)

Initialize service with name and key or just with name, creates new JWT token, assigns authLevel to context

func IssueJWT

func IssueJWT(claims *jwt.MapClaims) (string, error)

func LoadAuthLevel

func LoadAuthLevel(c *gin.Context)

Validate JWT token, GIN middleware

func RequireAuthLevel

func RequireAuthLevel(level GOETHE_AUTH_LEVEL) gin.HandlerFunc
  • Generates middleware function to require authLevel *
  • Usage:
  • gWithMiddleware := engine.Group("/RequiresAuthLevel", requireAuthLevel(goethe.AUTH_SERVICE_KEY))
  • {
  • endpoint_groups.RegisterAppGroup(appGroup)
  • }

func VerifyJWT

func VerifyJWT(c *gin.Context, tokenString string) (*jwt.Token, error)

Types

type GCallbacks

type GCallbacks struct {
	BeforeIssueJWT      func(c *gin.Context, claims *jwt.MapClaims) error
	OnVerifyCredentials func(name, key []byte) GOETHE_AUTH_LEVEL
	OnVerifyClaims      func(c *gin.Context, claims *jwt.MapClaims) error
}
var Callbacks *GCallbacks = nil

type GClaims

type GClaims struct {
	NeedReclaim bool
	Claims      jwt.MapClaims
}

allows us to define custom actions on jwt.MapClaims

func (*GClaims) NewResponse

func (G *GClaims) NewResponse(action []string, data map[string]interface{}, code int) *ResponseData

NewResponse creates a new ResponseData object

action: the action that was performed data: the data to be sent code: the HTTP status code

returns: a pointer to a ResponseData object

func (*GClaims) ReclaimJWT

func (JWT *GClaims) ReclaimJWT() (string, error)

func (*GClaims) RemoveClaim

func (JWT *GClaims) RemoveClaim(key string)

func (*GClaims) SetClaim

func (JWT *GClaims) SetClaim(key string, val interface{})

* Set JWT claim and mark it for reclaim

type GOETHE_AUTH_LEVEL

type GOETHE_AUTH_LEVEL uint8
const (
	AUTH_NONE              GOETHE_AUTH_LEVEL = iota // none authentication
	AUTH_SERVICE_NAME                               // valid service name provided
	AUTH_SERVICE_WRONG_KEY                          // service name and key, but key wrong
	AUTH_SERVICE_KEY                                // both service name and key were valid
	AUTH_USER_LOGGED_IN                             // user is logged in
)

type ResponseData

type ResponseData struct {
	Action []string    `json:"action"`
	Data   interface{} `json:"data"`
	Code   int         `json:"code"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL