title: Angular 8 CORS when connecting to Go Gin
body: <p>I built a backend with Golang's <a href="https://gin-gonic.com/docs/quickstart/" rel="nofollow noreferrer">Gin framework</a> and the <a href="https://github.com/appleboy/gin-jwt" rel="nofollow noreferrer">JWT middleware for it</a>. This is the <a href="https://github.com/appleboy/gin-jwt#example" rel="nofollow noreferrer">official example from the readme</a>, which I used:</p>
  
  <p><strong>main.go</strong></p>
  
  <pre><code>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(&amp;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 &amp;User{
                  UserName: claims[identityKey].(string),
              }
          },
          Authenticator: func(c *gin.Context) (interface{}, error) {
              var loginVals login
              if err := c.ShouldBind(&amp;loginVals); err != nil {
                  return "", jwt.ErrMissingLoginValues
              }
              userID := loginVals.Username
              password := loginVals.Password
  
              if (userID == "admin" &amp;&amp; password == "admin") || (userID == "test" &amp;&amp; password == "test") {
                  return &amp;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 &amp;&amp; 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 "&lt;source&gt;:&lt;name&gt;" that is used
          // to extract token from the request.
          // Optional. Default value "header:Authorization".
          // Possible values:
          // - "header:&lt;name&gt;"
          // - "query:&lt;name&gt;"
          // - "cookie:&lt;name&gt;"
          // - "param:&lt;name&gt;"
          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)
      }
  }
  </code></pre>
  
  <p>My auth service in Angular 8 looks like this:</p>
  
  <p><strong>auth.service</strong></p>
  
  <pre><code>headers = new HttpHeaders({ "Content-Type": "application/json" });
  
  login(username: string, password: string): Promise&lt;any&gt; {
      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;
  }
  </code></pre>
  
  <p>But this gives me an error message in Chrome:</p>
  
  <pre><code>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.
  </code></pre>
  
  <p>In the console Gin returns status code 204 though.</p>
  
  <p>I thought this was a CORS issue, so I implemented Gin's <a href="https://github.com/gin-contrib/cors#canonical-example" rel="nofollow noreferrer">CORS middleware</a>:</p>
  
  <pre><code>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,
  }))
  </code></pre>
  
  <p>Unfortunately it still didn't work. Also not if I added the POST method to the allowed methods:</p>
  
  <pre><code>AllowMethods:     []string{"PUT", "PATCH", "POST"}
  </code></pre>
  
  <p>My last try was to use proxy, as described in the <a href="https://angular.io/guide/build#proxying-to-a-backend-server" rel="nofollow noreferrer">Angular documentation</a>:</p>
  
  <p><strong>proxy.conf.json</strong></p>
  
  <pre><code>{
    "/api": {
      "target": "http://localhost:8000",
      "secure": false
    }
  }
  </code></pre>
  
  <p><strong>angular.json</strong></p>
  
  <pre><code>"options": {
        "browserTarget": "your-application-name:build",
        "proxyConfig": "src/proxy.conf.json"
   }
  </code></pre>
  
  <p>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).</p>
  
  <p>What am I doing wrong here?</p>