user3255061
user3255061

Reputation: 1815

Angular 8 CORS when connecting to Go Gin

I built a backend with Golang's Gin framework and the JWT middleware for it. This is the official example from the readme, which I used:

main.go

package main

import (
    "log"
    "net/http"
    "os"
    "time"

    "github.com/appleboy/gin-jwt/v2"
    "github.com/gin-gonic/gin"
)

type login struct {
    Username string `form:"username" json:"username" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}

var identityKey = "id"

func helloHandler(c *gin.Context) {
    claims := jwt.ExtractClaims(c)
    user, _ := c.Get(identityKey)
    c.JSON(200, gin.H{
        "userID":   claims[identityKey],
        "userName": user.(*User).UserName,
        "text":     "Hello World.",
    })
}

// User demo
type User struct {
    UserName  string
    FirstName string
    LastName  string
}

func main() {
    port := os.Getenv("PORT")
    r := gin.New()
    r.Use(gin.Logger())
    r.Use(gin.Recovery())

    if port == "" {
        port = "8000"
    }

    // the jwt middleware
    authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
        Realm:       "test zone",
        Key:         []byte("secret key"),
        Timeout:     time.Hour,
        MaxRefresh:  time.Hour,
        IdentityKey: identityKey,
        PayloadFunc: func(data interface{}) jwt.MapClaims {
            if v, ok := data.(*User); ok {
                return jwt.MapClaims{
                    identityKey: v.UserName,
                }
            }
            return jwt.MapClaims{}
        },
        IdentityHandler: func(c *gin.Context) interface{} {
            claims := jwt.ExtractClaims(c)
            return &User{
                UserName: claims[identityKey].(string),
            }
        },
        Authenticator: func(c *gin.Context) (interface{}, error) {
            var loginVals login
            if err := c.ShouldBind(&loginVals); err != nil {
                return "", jwt.ErrMissingLoginValues
            }
            userID := loginVals.Username
            password := loginVals.Password

            if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
                return &User{
                    UserName:  userID,
                    LastName:  "Bo-Yi",
                    FirstName: "Wu",
                }, nil
            }

            return nil, jwt.ErrFailedAuthentication
        },
        Authorizator: func(data interface{}, c *gin.Context) bool {
            if v, ok := data.(*User); ok && v.UserName == "admin" {
                return true
            }

            return false
        },
        Unauthorized: func(c *gin.Context, code int, message string) {
            c.JSON(code, gin.H{
                "code":    code,
                "message": message,
            })
        },
        // TokenLookup is a string in the form of "<source>:<name>" that is used
        // to extract token from the request.
        // Optional. Default value "header:Authorization".
        // Possible values:
        // - "header:<name>"
        // - "query:<name>"
        // - "cookie:<name>"
        // - "param:<name>"
        TokenLookup: "header: Authorization, query: token, cookie: jwt",
        // TokenLookup: "query:token",
        // TokenLookup: "cookie:token",

        // TokenHeadName is a string in the header. Default value is "Bearer"
        TokenHeadName: "Bearer",

        // TimeFunc provides the current time. You can override it to use another time value. This is useful for testing or if your server uses a different time zone than your tokens.
        TimeFunc: time.Now,
    })

    if err != nil {
        log.Fatal("JWT Error:" + err.Error())
    }

    r.POST("/login", authMiddleware.LoginHandler)

    r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
        claims := jwt.ExtractClaims(c)
        log.Printf("NoRoute claims: %#v\n", claims)
        c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
    })

    auth := r.Group("/auth")
    // Refresh time can be longer than token timeout
    auth.GET("/refresh_token", authMiddleware.RefreshHandler)
    auth.Use(authMiddleware.MiddlewareFunc())
    {
        auth.GET("/hello", helloHandler)
    }

    if err := http.ListenAndServe(":"+port, r); err != nil {
        log.Fatal(err)
    }
}

My auth service in Angular 8 looks like this:

auth.service

headers = new HttpHeaders({ "Content-Type": "application/json" });

login(username: string, password: string): Promise<any> {
    const url: string = `${this.BASE_URL}` + "/login";
    const request = this.http
        .post(
            url,
            JSON.stringify({ username: username, password: password }),
            { headers: this.headers }
        )

        .toPromise();

    return request;
}

But this gives me an error message in Chrome:

Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/login' from origin 'http://localhost:4200' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

In the console Gin returns status code 204 though.

I thought this was a CORS issue, so I implemented Gin's CORS middleware:

r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:8000"},
    AllowMethods:     []string{"PUT", "PATCH"},
    AllowHeaders:     []string{"Origin"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {
        return origin == "https://github.com"
    },
    MaxAge: 12 * time.Hour,
}))

Unfortunately it still didn't work. Also not if I added the POST method to the allowed methods:

AllowMethods:     []string{"PUT", "PATCH", "POST"}

My last try was to use proxy, as described in the Angular documentation:

proxy.conf.json

{
  "/api": {
    "target": "http://localhost:8000",
    "secure": false
  }
}

angular.json

"options": {
      "browserTarget": "your-application-name:build",
      "proxyConfig": "src/proxy.conf.json"
 }

I re-started with ng serve, but the error still appears (I changed the urls to /api/login in the auth.service and main.go according to the configuration in the proxy file).

What am I doing wrong here?

Upvotes: 0

Views: 1960

Answers (1)

sideshowbarker
sideshowbarker

Reputation: 88286

Change AllowHeaders: []string{"Origin"} to AllowHeaders: []string{"content-type"};

Upvotes: 2

Related Questions