Skip to content

Commit

Permalink
Implement authentication for api gateway with api tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
blaz-cerpnjak committed Apr 17, 2024
1 parent f315dac commit 1b3054f
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 33 deletions.
6 changes: 6 additions & 0 deletions API_GatewayWeb/DataStructures/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package DataStructures

import (
"github.com/dgrijalva/jwt-go"
"go.mongodb.org/mongo-driver/bson/primitive"
)

type ApiKey struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
jwt.StandardClaims
}

type User struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Name string `json:"name" bson:"name"`
Expand Down
26 changes: 26 additions & 0 deletions API_GatewayWeb/HTTP_API/Authentication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package HTTP_API

import (
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)

func (a *Controller) generateApiToken(c *gin.Context) {

id, err := primitive.ObjectIDFromHex(c.Param("userId"))
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}

expirationTime := time.Now().AddDate(0, 0, 30)

tokenString, err := a.logic.GenerateApiToken(id, expirationTime)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}

c.JSON(200, gin.H{"api_token": tokenString})
}
62 changes: 62 additions & 0 deletions API_GatewayWeb/HTTP_API/Middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package HTTP_API

import (
"errors"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson/primitive"
"net/http"
"strings"
)

/*
Middleware for checking api key/token for each request.
*/
func (a *Controller) checkAuth() gin.HandlerFunc {
return func(c *gin.Context) {
var jwtString string
var err error

tokenHeader := c.GetHeader("Authorization")
if len(tokenHeader) > 0 {
headerArr := strings.Split(tokenHeader, " ")
if len(headerArr) != 2 {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
jwtString = headerArr[1]
} else {
jwtString, err = c.Cookie("JWT")
if err != nil || jwtString == "" {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
}

token, err := jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
c.AbortWithStatus(http.StatusUnauthorized)
return nil, errors.New("api token is not valid")
}
return a.jwtSecret, nil
})
if err != nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
userId, err := primitive.ObjectIDFromHex(claims["id"].(string))
if err != nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
}

c.Set("userId", userId)
} else {
c.AbortWithStatus(http.StatusUnauthorized)
return
}

c.Next()
}
}
3 changes: 3 additions & 0 deletions API_GatewayWeb/HTTP_API/Routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ func (a *Controller) registerRoutes(engine *gin.Engine) {

api := engine.Group("/api/v1")
api.GET("/", a.Ping)
api.GET("/token/generate/:userId", a.generateApiToken)

api.Use(a.checkAuth())

a.registerUserRoutes(api.Group("/users"))
a.registerRestaurantRoutes(api.Group("/restaurants"))
Expand Down
10 changes: 6 additions & 4 deletions API_GatewayWeb/HTTP_API/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import (
)

type Controller struct {
logic *Logic.Controller
done chan bool
logic *Logic.Controller
jwtSecret []byte
done chan bool
}

func New(logic *Logic.Controller) *Controller {
func New(logic *Logic.Controller, jwtSecret string) *Controller {
return &Controller{
logic: logic,
logic: logic,
jwtSecret: []byte(jwtSecret),
}
}

Expand Down
33 changes: 33 additions & 0 deletions API_GatewayWeb/Logic/Authentication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package Logic

import (
"API_GatewayWeb/DataStructures"
"fmt"
"github.com/dgrijalva/jwt-go"
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)

func (c *Controller) GenerateApiToken(userId primitive.ObjectID, expirationTime time.Time) (tokenString string, err error) {

// First should also check if the user exists in the database
// and has paid for the service or has the right to access the service, etc.
// We should also save api tokens in key-value store like Redis so that we can
// invalidate the token if needed.

tk := &DataStructures.ApiKey{
Id: userId,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
}}

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

tokenString, err = token.SignedString(c.jwtSecret)
if err != nil {
fmt.Println(err.Error())
return
}

return
}
4 changes: 3 additions & 1 deletion API_GatewayWeb/Logic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import (
type Controller struct {
httpClient *http.Client
grpc *gRPC_Client.Controller
jwtSecret []byte
}

func New(grpc *gRPC_Client.Controller) (*Controller, error) {
func New(grpc *gRPC_Client.Controller, jwtSecret string) (*Controller, error) {
httpClient := &http.Client{}

return &Controller{
httpClient: httpClient,
grpc: grpc,
jwtSecret: []byte(jwtSecret),
}, nil
}

Expand Down
4 changes: 2 additions & 2 deletions API_GatewayWeb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ func main() {

fmt.Println("gRPC connection established")

logicController, err := Logic.New(grpcClient)
logicController, err := Logic.New(grpcClient, getEnv("JWT_SECRET", ""))
if err != nil {
fmt.Println(err.Error())
return
}

httpAPI := HTTP_API.New(logicController)
httpAPI := HTTP_API.New(logicController, getEnv("JWT_SECRET", ""))
httpAPI.Start()

quit := make(chan os.Signal, 0)
Expand Down
1 change: 1 addition & 0 deletions WEB_Microfrontends/basket/src/services/OrderService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const createOrder = (order) =>
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${process.env.API_TOKEN}`,
},
body: JSON.stringify(order)
}).then((res) => {
Expand Down
4 changes: 2 additions & 2 deletions WEB_Microfrontends/home/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Navbar from "./Navbar";
import ProductsPage from "./ProductsPage";
import {createRoot} from "react-dom/client";
import {BasketProvider, useBasket} from "./context/BasketContext";
import BasketPage2 from "./BasketPage";
import BasketPage from "./BasketPage";

const App = () => (
<PrimeReactProvider>
Expand All @@ -25,7 +25,7 @@ const App = () => (
<Routes>
<Route exact path="/" element={<ProductsPage />} />
<Route path="/orders" element={<OrderHistoryItems />} />
<Route path="/basket" element={<BasketPage2 />} />
<Route path="/basket" element={<BasketPage />} />
</Routes>
</div>
</div>
Expand Down
5 changes: 2 additions & 3 deletions WEB_Microfrontends/home/src/Navbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useBasket } from "./context/BasketContext";

export default function Navbar() {
const { state: basketState } = useBasket();
const badgeNumber = basketState.items.length;
const basketBadgeNumber = basketState.items.length;

const itemRenderer = (item) => (
<Link to={item.path} className="flex align-items-center p-menuitem-link">
Expand All @@ -28,14 +28,13 @@ export default function Navbar() {
label: 'Orders',
icon: 'pi pi-envelope',
path: '/orders',
badge: 3,
template: itemRenderer
},
{
label: 'Basket',
icon: 'pi pi-shopping-cart',
path: '/basket',
badge: badgeNumber,
badge: basketBadgeNumber,
template: itemRenderer
}
];
Expand Down
20 changes: 0 additions & 20 deletions WEB_Microfrontends/home/src/services/OrdersService.js

This file was deleted.

3 changes: 2 additions & 1 deletion WEB_Microfrontends/home/src/services/ProductsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export const getProducts = () =>
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${process.env.API_TOKEN}`,
},
}).then((res) => res.json());
}).then((res) => res.json())
2 changes: 2 additions & 0 deletions WEB_Microfrontends/orders/src/services/OrdersService.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const getOrders = (order) =>
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${process.env.API_TOKEN}`,
}
}).then((res) => {
if (!res.ok) {
Expand All @@ -18,6 +19,7 @@ export const cancelOrder = (order) =>
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${process.env.API_TOKEN}`,
},
body: JSON.stringify({
...order,
Expand Down
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ services:
dockerfile: Dockerfile
restart: always
environment:
JWT_SECRET: A9BBD31CB41A4629875353E3A1AA9
USERS_API: http://user-management-api:8080/api/v1
RESTAURANTS_API: http://restaurant-management-api:8080
ORDERS_GRPC_API: order-processing-grpc-api:9000
Expand Down Expand Up @@ -117,6 +118,8 @@ services:
build:
context: ./WEB_Microfrontends/orders
dockerfile: Dockerfile
environment:
API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MWZlYjU2YjI2ZTEwYjRjMDkzNzZjNiIsImV4cCI6NjQ0OTQ3MDg3OH0.fOWJzwFCdb6pWJkr8wJUxW1bvZ2PWsrU4qjanFq6tpU
ports:
- "3001:3001"
networks:
Expand All @@ -129,6 +132,8 @@ services:
build:
context: ./WEB_Microfrontends/basket
dockerfile: Dockerfile
environment:
API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MWZlYjU2YjI2ZTEwYjRjMDkzNzZjNiIsImV4cCI6NjQ0OTQ3MDg3OH0.fOWJzwFCdb6pWJkr8wJUxW1bvZ2PWsrU4qjanFq6tpU
ports:
- "3002:3002"
networks:
Expand All @@ -141,6 +146,8 @@ services:
build:
context: ./WEB_Microfrontends/home
dockerfile: Dockerfile
environment:
API_TOKEN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY2MWZlYjU2YjI2ZTEwYjRjMDkzNzZjNiIsImV4cCI6NjQ0OTQ3MDg3OH0.fOWJzwFCdb6pWJkr8wJUxW1bvZ2PWsrU4qjanFq6tpU
ports:
- "3000:3000"
networks:
Expand Down

0 comments on commit 1b3054f

Please sign in to comment.