github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/safehttp/plugins/xsrf/xsrfangular/xsrf.go (about) 1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package xsrfangular 16 17 import ( 18 "crypto/rand" 19 "encoding/base64" 20 "fmt" 21 "time" 22 23 "github.com/google/go-safeweb/safehttp" 24 "github.com/google/go-safeweb/safehttp/plugins/xsrf" 25 ) 26 27 // Interceptor provides protection against Cross-Site Request Forgery attacks 28 // for Angular's XHR requests. 29 // 30 // See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details. 31 type Interceptor struct { 32 // TokenCookieName is the name of the session cookie that holds the XSRF token. 33 TokenCookieName string 34 // TokenHeaderName is the name of the HTTP header that holds the XSRF token. 35 TokenHeaderName string 36 } 37 38 var _ safehttp.Interceptor = &Interceptor{} 39 40 // Default creates an Interceptor with TokenCookieName set to XSRF-TOKEN and 41 // TokenHeaderName set to X-XSRF-TOKEN, their default values. However, in order 42 // to prevent collisions when multiple applications share the same domain or 43 // subdomain, each application should set a unique name for the cookie. 44 // 45 // See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details. 46 func Default() *Interceptor { 47 return &Interceptor{ 48 TokenCookieName: "XSRF-TOKEN", 49 TokenHeaderName: "X-XSRF-TOKEN", 50 } 51 } 52 53 // Before checks for the presence of a matching XSRF token, generated on the 54 // first page access, in both a cookie and a header. Their names should be set 55 // when the Interceptor is created. 56 func (it *Interceptor) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result { 57 if xsrf.StatePreserving(r) { 58 return safehttp.NotWritten() 59 } 60 61 c, err := r.Cookie(it.TokenCookieName) 62 if err != nil || c.Value() == "" { 63 return w.WriteError(safehttp.StatusForbidden) 64 } 65 66 tok := r.Header.Get(it.TokenHeaderName) 67 if tok == "" || tok != c.Value() { 68 // JavaScript has access only to cookies from the domain it's running 69 // on. Hence, if the same token is found in both the cookie and the 70 // header, the request can be trusted. 71 return w.WriteError(safehttp.StatusUnauthorized) 72 } 73 74 return safehttp.NotWritten() 75 } 76 77 func (it *Interceptor) addTokenCookie(w safehttp.ResponseHeadersWriter) error { 78 tok := make([]byte, 20) 79 if _, err := rand.Read(tok); err != nil { 80 return fmt.Errorf("crypto/rand.Read: %v", err) 81 } 82 c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok)) 83 84 c.SameSite(safehttp.SameSiteStrictMode) 85 c.Path("/") 86 day := 24 * time.Hour 87 c.SetMaxAge(int(day.Seconds())) 88 // Needed in order to make the cookie accessible by JavaScript 89 // running on the same domain. 90 c.DisableHTTPOnly() 91 92 return w.AddCookie(c) 93 } 94 95 // Commit generates a cryptographically secure random cookie on the first state 96 // preserving request (GET, HEAD or OPTION) and sets it in the response. On 97 // every subsequent request the cookie is expected alongside a header that 98 // matches its value. 99 func (it *Interceptor) Commit(w safehttp.ResponseHeadersWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) { 100 if c, err := r.Cookie(it.TokenCookieName); err == nil && c.Value() != "" { 101 // The XSRF cookie is there so we don't need to do anything else. 102 return 103 } 104 105 if !xsrf.StatePreserving(r) { 106 // Not a state preserving request, so we won't be adding the cookie. 107 return 108 } 109 110 if err := it.addTokenCookie(w); err != nil { 111 // This is a server misconfiguration. 112 panic("cannot add token cookie") 113 } 114 } 115 116 // Match returns false since there are no supported configurations. 117 func (*Interceptor) Match(safehttp.InterceptorConfig) bool { 118 return false 119 }