README.md
1# Secure [![GoDoc](https://godoc.org/github.com/unrolled/secure?status.svg)](http://godoc.org/github.com/unrolled/secure) [![Test](https://github.com/unrolled/secure/workflows/Test/badge.svg?branch=v1)](https://github.com/unrolled/secure/actions)
2
3Secure is an HTTP middleware for Go that facilitates some quick security wins. It's a standard net/http [Handler](http://golang.org/pkg/net/http/#Handler), and can be used with many [frameworks](#integration-examples) or directly with Go's net/http package.
4
5## Usage
6
7~~~ go
8// main.go
9package main
10
11import (
12 "net/http"
13
14 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
15)
16
17var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18 w.Write([]byte("hello world"))
19})
20
21func main() {
22 secureMiddleware := secure.New(secure.Options{
23 AllowedHosts: []string{"example\\.com", ".*\\.example\\.com"},
24 AllowedHostsAreRegex: true,
25 HostsProxyHeaders: []string{"X-Forwarded-Host"},
26 SSLRedirect: true,
27 SSLHost: "ssl.example.com",
28 SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"},
29 STSSeconds: 31536000,
30 STSIncludeSubdomains: true,
31 STSPreload: true,
32 FrameDeny: true,
33 ContentTypeNosniff: true,
34 BrowserXssFilter: true,
35 ContentSecurityPolicy: "script-src $NONCE",
36 })
37
38 app := secureMiddleware.Handler(myHandler)
39 http.ListenAndServe("127.0.0.1:3000", app)
40}
41~~~
42
43Be sure to include the Secure middleware as close to the top (beginning) as possible (but after logging and recovery). It's best to do the allowed hosts and SSL check before anything else.
44
45The above example will only allow requests with a host name of 'example.com', or 'ssl.example.com'. Also if the request is not HTTPS, it will be redirected to HTTPS with the host name of 'ssl.example.com'.
46Once those requirements are satisfied, it will add the following headers:
47~~~ go
48Strict-Transport-Security: 31536000; includeSubdomains; preload
49X-Frame-Options: DENY
50X-Content-Type-Options: nosniff
51X-XSS-Protection: 1; mode=block
52Content-Security-Policy: script-src 'nonce-a2ZobGFoZg=='
53~~~
54
55### Set the `IsDevelopment` option to `true` when developing!
56When `IsDevelopment` is true, the AllowedHosts, SSLRedirect, STS header, and HPKP header will not be in effect. This allows you to work in development/test mode and not have any annoying redirects to HTTPS (ie. development can happen on HTTP), or block `localhost` has a bad host.
57
58### Available options
59Secure comes with a variety of configuration options (Note: these are not the default option values. See the defaults below.):
60
61~~~ go
62// ...
63s := secure.New(secure.Options{
64 AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names.
65 AllowedHostsAreRegex: false, // AllowedHostsAreRegex determines, if the provided AllowedHosts slice contains valid regular expressions. Default is false.
66 HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
67 SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false.
68 SSLTemporaryRedirect: false, // If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301).
69 SSLHost: "ssl.example.com", // SSLHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host.
70 SSLHostFunc: nil, // SSLHostFunc is a function pointer, the return value of the function is the host name that has same functionality as `SSHost`. Default is nil. If SSLHostFunc is nil, the `SSLHost` option will be used.
71 SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, // SSLProxyHeaders is set of header keys with associated values that would indicate a valid HTTPS request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map.
72 STSSeconds: 31536000, // STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header.
73 STSIncludeSubdomains: true, // If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false.
74 STSPreload: true, // If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false.
75 ForceSTSHeader: false, // STS header is only included when the connection is HTTPS. If you want to force it to always be added, set to true. `IsDevelopment` still overrides this. Default is false.
76 FrameDeny: true, // If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false.
77 CustomFrameOptionsValue: "SAMEORIGIN", // CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option. Default is "".
78 ContentTypeNosniff: true, // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false.
79 BrowserXssFilter: true, // If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false.
80 CustomBrowserXssValue: "1; report=https://example.com/xss-report", // CustomBrowserXssValue allows the X-XSS-Protection header value to be set with a custom value. This overrides the BrowserXssFilter option. Default is "".
81 ContentSecurityPolicy: "default-src 'self'", // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "". Passing a template string will replace `$NONCE` with a dynamic nonce value of 16 bytes for each request which can be later retrieved using the Nonce function.
82 PublicKey: `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`, // Deprecated: This feature is no longer recommended. PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "".
83 ReferrerPolicy: "same-origin", // ReferrerPolicy allows the Referrer-Policy header with the value to be set with a custom value. Default is "".
84 FeaturePolicy: "vibrate 'none';", // Deprecated: this header has been renamed to PermissionsPolicy. FeaturePolicy allows the Feature-Policy header with the value to be set with a custom value. Default is "".
85 PermissionsPolicy: "fullscreen=(), geolocation=()", // PermissionsPolicy allows the Permissions-Policy header with the value to be set with a custom value. Default is "".
86 ExpectCTHeader: `enforce, max-age=30, report-uri="https://www.example.com/ct-report"`,
87
88 IsDevelopment: true, // This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false.
89})
90// ...
91~~~
92
93### Default options
94These are the preset options for Secure:
95
96~~~ go
97s := secure.New()
98
99// Is the same as the default configuration options:
100
101l := secure.New(secure.Options{
102 AllowedHosts: []string,
103 AllowedHostsAreRegex: false,
104 HostsProxyHeaders: []string,
105 SSLRedirect: false,
106 SSLTemporaryRedirect: false,
107 SSLHost: "",
108 SSLProxyHeaders: map[string]string{},
109 STSSeconds: 0,
110 STSIncludeSubdomains: false,
111 STSPreload: false,
112 ForceSTSHeader: false,
113 FrameDeny: false,
114 CustomFrameOptionsValue: "",
115 ContentTypeNosniff: false,
116 BrowserXssFilter: false,
117 ContentSecurityPolicy: "",
118 PublicKey: "",
119 ReferrerPolicy: "",
120 FeaturePolicy: "",
121 PermissionsPolicy: "",
122 ExpectCTHeader: "",
123 IsDevelopment: false,
124})
125~~~
126Also note the default bad host handler returns an error:
127~~~ go
128http.Error(w, "Bad Host", http.StatusInternalServerError)
129~~~
130Call `secure.SetBadHostHandler` to change the bad host handler.
131
132### Redirecting HTTP to HTTPS
133If you want to redirect all HTTP requests to HTTPS, you can use the following example.
134
135~~~ go
136// main.go
137package main
138
139import (
140 "log"
141 "net/http"
142
143 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
144)
145
146var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
147 w.Write([]byte("hello world"))
148})
149
150func main() {
151 secureMiddleware := secure.New(secure.Options{
152 SSLRedirect: true,
153 SSLHost: "localhost:8443", // This is optional in production. The default behavior is to just redirect the request to the HTTPS protocol. Example: http://github.com/some_page would be redirected to https://github.com/some_page.
154 })
155
156 app := secureMiddleware.Handler(myHandler)
157
158 // HTTP
159 go func() {
160 log.Fatal(http.ListenAndServe(":8080", app))
161 }()
162
163 // HTTPS
164 // To generate a development cert and key, run the following from your *nix terminal:
165 // go run $GOROOT/src/crypto/tls/generate_cert.go --host="localhost"
166 log.Fatal(http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", app))
167}
168~~~
169
170### Strict Transport Security
171The STS header will only be sent on verified HTTPS connections (and when `IsDevelopment` is false). Be sure to set the `SSLProxyHeaders` option if your application is behind a proxy to ensure the proper behavior. If you need the STS header for all HTTP and HTTPS requests (which you [shouldn't](http://tools.ietf.org/html/rfc6797#section-7.2)), you can use the `ForceSTSHeader` option. Note that if `IsDevelopment` is true, it will still disable this header even when `ForceSTSHeader` is set to true.
172
173* The `preload` flag is required for domain inclusion in Chrome's [preload](https://hstspreload.appspot.com/) list.
174
175### Content Security Policy
176If you need dynamic support for CSP while using Websockets, check out this other middleware [awakenetworks/csp](https://github.com/awakenetworks/csp).
177
178## Integration examples
179
180### [chi](https://github.com/pressly/chi)
181~~~ go
182// main.go
183package main
184
185import (
186 "net/http"
187
188 "github.com/pressly/chi"
189 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
190)
191
192func main() {
193 secureMiddleware := secure.New(secure.Options{
194 FrameDeny: true,
195 })
196
197 r := chi.NewRouter()
198 r.Use(secureMiddleware.Handler)
199
200 r.Get("/", func(w http.ResponseWriter, r *http.Request) {
201 w.Write([]byte("X-Frame-Options header is now `DENY`."))
202 })
203
204 http.ListenAndServe("127.0.0.1:3000", r)
205}
206~~~
207
208### [Echo](https://github.com/labstack/echo)
209~~~ go
210// main.go
211package main
212
213import (
214 "net/http"
215
216 "github.com/labstack/echo"
217 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
218)
219
220func main() {
221 secureMiddleware := secure.New(secure.Options{
222 FrameDeny: true,
223 })
224
225 e := echo.New()
226 e.GET("/", func(c echo.Context) error {
227 return c.String(http.StatusOK, "X-Frame-Options header is now `DENY`.")
228 })
229
230 e.Use(echo.WrapMiddleware(secureMiddleware.Handler))
231 e.Logger.Fatal(e.Start("127.0.0.1:3000"))
232}
233~~~
234
235### [Gin](https://github.com/gin-gonic/gin)
236~~~ go
237// main.go
238package main
239
240import (
241 "github.com/gin-gonic/gin"
242 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
243)
244
245func main() {
246 secureMiddleware := secure.New(secure.Options{
247 FrameDeny: true,
248 })
249 secureFunc := func() gin.HandlerFunc {
250 return func(c *gin.Context) {
251 err := secureMiddleware.Process(c.Writer, c.Request)
252
253 // If there was an error, do not continue.
254 if err != nil {
255 c.Abort()
256 return
257 }
258
259 // Avoid header rewrite if response is a redirection.
260 if status := c.Writer.Status(); status > 300 && status < 399 {
261 c.Abort()
262 }
263 }
264 }()
265
266 router := gin.Default()
267 router.Use(secureFunc)
268
269 router.GET("/", func(c *gin.Context) {
270 c.String(200, "X-Frame-Options header is now `DENY`.")
271 })
272
273 router.Run("127.0.0.1:3000")
274}
275~~~
276
277### [Goji](https://github.com/zenazn/goji)
278~~~ go
279// main.go
280package main
281
282import (
283 "net/http"
284
285 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
286 "github.com/zenazn/goji"
287 "github.com/zenazn/goji/web"
288)
289
290func main() {
291 secureMiddleware := secure.New(secure.Options{
292 FrameDeny: true,
293 })
294
295 goji.Get("/", func(c web.C, w http.ResponseWriter, req *http.Request) {
296 w.Write([]byte("X-Frame-Options header is now `DENY`."))
297 })
298 goji.Use(secureMiddleware.Handler)
299 goji.Serve() // Defaults to ":8000".
300}
301~~~
302
303### [Iris](https://github.com/kataras/iris)
304~~~ go
305//main.go
306package main
307
308import (
309 "github.com/kataras/iris/v12"
310 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
311)
312
313func main() {
314 app := iris.New()
315
316 secureMiddleware := secure.New(secure.Options{
317 FrameDeny: true,
318 })
319
320 app.Use(iris.FromStd(secureMiddleware.HandlerFuncWithNext))
321 // Identical to:
322 // app.Use(func(ctx iris.Context) {
323 // err := secureMiddleware.Process(ctx.ResponseWriter(), ctx.Request())
324 //
325 // // If there was an error, do not continue.
326 // if err != nil {
327 // return
328 // }
329 //
330 // ctx.Next()
331 // })
332
333 app.Get("/home", func(ctx iris.Context) {
334 ctx.Writef("X-Frame-Options header is now `%s`.", "DENY")
335 })
336
337 app.Listen(":8080")
338}
339~~~
340
341### [Mux](https://github.com/gorilla/mux)
342~~~ go
343//main.go
344package main
345
346import (
347 "log"
348 "net/http"
349
350 "github.com/gorilla/mux"
351 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
352)
353
354func main() {
355 secureMiddleware := secure.New(secure.Options{
356 FrameDeny: true,
357 })
358
359 r := mux.NewRouter()
360 r.Use(secureMiddleware.Handler)
361 http.Handle("/", r)
362 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", 8080), nil))
363}
364~~~
365
366### [Negroni](https://github.com/urfave/negroni)
367Note this implementation has a special helper function called `HandlerFuncWithNext`.
368~~~ go
369// main.go
370package main
371
372import (
373 "net/http"
374
375 "github.com/urfave/negroni"
376 "github.com/unrolled/secure" // or "gopkg.in/unrolled/secure.v1"
377)
378
379func main() {
380 mux := http.NewServeMux()
381 mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
382 w.Write([]byte("X-Frame-Options header is now `DENY`."))
383 })
384
385 secureMiddleware := secure.New(secure.Options{
386 FrameDeny: true,
387 })
388
389 n := negroni.Classic()
390 n.Use(negroni.HandlerFunc(secureMiddleware.HandlerFuncWithNext))
391 n.UseHandler(mux)
392
393 n.Run("127.0.0.1:3000")
394}
395~~~
396