Giter VIP home page Giter VIP logo

tokenauth's Introduction

Go Report Card GitHub issues GitHub license

๐Ÿ”’ Token Authentication

Token Authentication middleware for Fiber that provides a basic token authentication.

๐Ÿ“ Table of contents

Examples

Token Generation


IN MEMORY

Example for in memory token storage:

package main

import (
	"github.com/gofiber/fiber/v2"
	"github.com/klipitkas/tokenauth"
)

var Tokens = map[string]tokenauth.Claims{
	"token": {"user": "john", "email": "[email protected]", "id": "42"},
}

func main() {
	app := fiber.New()

	app.Use(tokenauth.New(tokenauth.Config{
		Authorizer: func(s string) tokenauth.Claims {
			claims, exist := Tokens[s]
			if !exist {
				return nil
			}
			return claims
		},
	}))

	app.Get("/", func(c *fiber.Ctx) error {
		claims := c.Locals("claims").(tokenauth.Claims)
		return c.SendString("Hello, " + claims["user"] + " ๐Ÿ‘‹!")
	})

	_ = app.Listen(":3000")
}

Try to access the route without a token:

$ curl http://localhost:3000
Unauthorized

Try to access the route after providing a token:

$ curl -H 'Authorization: Bearer token' http://localhost:3000
Hello, john ๐Ÿ‘‹!

DATABASES

POSTGRES

Use docker to launch a new ephemeral postgres database:

$ docker run -p 5432:5432 -e POSTGRES_PASSWORD=tokenauth -e POSTGRES_USER=tokenauth -e POSTGRES_DB=tokenauth -d postgres

Try the following example:

package main

import (
	"database/sql"
	"fmt"
	"log"

	"github.com/gofiber/fiber/v2"
	"github.com/klipitkas/tokenauth"

	_ "github.com/lib/pq"
)

const (
	host   = "localhost"
	port   = 5432
	user   = "tokenauth"
	pass   = "tokenauth"
	dbname = "tokenauth"
)

var sqlMigration string = `
DROP TABLE IF EXISTS user_tokens;
DROP TABLE IF EXISTS users;

CREATE TABLE users (
	id INT,
	email TEXT,
	PRIMARY KEY(id)
);

CREATE TABLE user_tokens (
	id INT,
	user_id INT,
	token TEXT,
	PRIMARY KEY(id),
	UNIQUE(token),
	CONSTRAINT fk_customer FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
);`

var sqlSeeder string = `
	INSERT INTO users VALUES (1, '[email protected]');
	INSERT INTO users VALUES (2, '[email protected]');

	INSERT INTO user_tokens VALUES (1, 1, 'token');
	INSERT INTO user_tokens VALUES (2, 2, 'fgu9KILmznhLtQgmr3');
`

func main() {
	app := fiber.New()

	// Connect to the database.
	db, err := dbConnect(host, user, pass, dbname, port)
	if err != nil {
		log.Fatalf("Failed to connect to db: %v", err)
	}

	// Run the migrations.
	if _, err = db.Exec(sqlMigration); err != nil {
		log.Fatalf("Failed to run the migrations: %v", err)
	}

	// Run the seeders.
	if _, err = db.Exec(sqlSeeder); err != nil {
		log.Fatalf("Failed to run the migrations: %v", err)
	}

	// Use our custom authorizer to verify tokens from the DB.
	app.Use(tokenauth.New(tokenauth.Config{Authorizer: dbTokenAuthorizer}))

	// Our protected route
	app.Get("/", func(c *fiber.Ctx) error {
		claims := c.Locals("claims").(tokenauth.Claims)
		return c.SendString("Hello, user with ID: " + claims["user_id"] + " ๐Ÿ‘‹!")
	})

	_ = app.Listen(":3000")

	defer db.Close()
}

func dbConnect(host, user, pass, dbname string, port int) (*sql.DB, error) {
	connStr := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, pass, dbname)

	// Validate the connection arguments
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		return nil, fmt.Errorf("connect to db: %v", err)
	}

	if err = db.Ping(); err != nil {
		return nil, fmt.Errorf("ping the db: %v", err)
	}

	return db, nil
}

func dbTokenAuthorizer(token string) tokenauth.Claims {
	// Connect to the database.
	db, err := dbConnect(host, user, pass, dbname, port)
	if err != nil {
		fmt.Printf("connect to db: %v", err)
		return nil
	}
	defer db.Close()

	// Get the user id if the token is valid.
	// You can do more things here, such as JOIN statements with the users
	// table and get more information for the user.
	rows, err := db.Query("SELECT user_id FROM user_tokens WHERE token = $1", token)
	if err != nil || rows.Err() != nil {
		fmt.Printf("run query for tokens: %v", err)
		return nil
	}

	// Set a default empty user ID.
	userID := ""
	for rows.Next() {
		if err := rows.Scan(&userID); err != nil {
			fmt.Printf("get token rows: %v", err)
			return nil
		}
	}

	// Check if we got a valid (non-zero length) user id.
	if len(userID) > 0 {
		return tokenauth.Claims{"user_id": userID}
	}

	return nil
}

Try to access the route without a token:

$ curl http://localhost:3000
Unauthorized

Try to access the route after providing a token:

$ curl -H 'Authorization: Bearer token' http://localhost:3000
Hello, user with ID: 1 ๐Ÿ‘‹!
$ curl -H 'Authorization: Bearer fgu9KILmznhLtQgmr3' http://localhost:3000
Hello, user with ID: 2 ๐Ÿ‘‹!

REDIS

Use docker to launch a new ephemeral redis instance:

$ docker run -p 6379:6379 --name redis -d redis

Try the following example:

package main

import (
	"github.com/gofiber/fiber/v2"
	"github.com/klipitkas/tokenauth"

	"context"

	"github.com/go-redis/redis/v8"
)

func main() {
	app := fiber.New()

	// Use our custom authorizer to verify tokens from Redis.
	app.Use(tokenauth.New(tokenauth.Config{Authorizer: redisTokenAuthorizer}))

	// Our protected route
	app.Get("/", func(c *fiber.Ctx) error {
		claims := c.Locals("claims").(tokenauth.Claims)
		return c.SendString("Hello, user with details: " + claims["user"] + " !๐Ÿ‘‹")
	})

	_ = app.Listen(":3000")
}

func redisTokenAuthorizer(token string) tokenauth.Claims {
	// Create a new redis client and connect to Redis instance.
	redis := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})
	defer redis.Close()

	// Test if connection to Redis works.
	if _, err := redis.Ping(context.Background()).Result(); err != nil {
		return nil
	}

	// Fetch the user details using the token as a key.
	user, err := redis.Get(context.Background(), token).Result()
	if err != nil {
		return nil
	}

	if len(user) > 0 {
		return tokenauth.Claims{"user": user}
	}

	return nil
}

Now you can create a new token using redis-cli:

$  docker exec -it redis redis-cli
127.0.0.1:6379> set token test
OK

Try to access the route without a token:

$ curl http://localhost:3000
Unauthorized

Try to access the route after providing a token:

$ curl -H 'Authorization: Bearer token' http://localhost:3000
Hello, user with details: test ๐Ÿ‘‹!

Default Token Generation

To generate a new token with the default config:

package main

import (
	"fmt"
	"log"

	"github.com/klipitkas/tokenauth"
)

func main() {
	token, err := tokenauth.NewToken(tokenauth.TokenConfig{})
	if err != nil {
		log.Fatalf("Token generation failed: %v", err)
	}
	fmt.Printf("Token: %s", token)
}

You can specify the following parameters as the token configuration:

// TokenConfig is the struct that contains the config
// for token generation.
type TokenConfig struct {
	// Alphabet contains the characters that can be used as parts of the token.
	Alphabet string
	// Length is the length of the token to generate.
	Length int
	// Generator is the function that can be used to generate tokens.
	Generator func(int, string) (string, error)
}

Custom Token Generation

If you want to use a custom function to generate a token:

package main

import (
	"fmt"
	"log"

	"github.com/klipitkas/tokenauth"
)

func main() {
	token, err := tokenauth.NewToken(tokenauth.TokenConfig{
		Generator: ultraSecureTokenGenerator,
	})
	if err != nil {
		log.Fatalf("Token generation failed: %v", err)
	}
	fmt.Printf("Token: %s", token)
}

func ultraSecureTokenGenerator(length int, alphabet string) (string, error) {
	return "token", nil
}

tokenauth's People

Contributors

dependabot-preview[bot] avatar klipitkas avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.