github.com/System-Glitch/goyave/v2@v2.10.3-0.20200819142921-51011e75d504/docs_src/src/guide/advanced/authentication.md (about) 1 --- 2 meta: 3 - name: "og:title" 4 content: "Authentication - Goyave" 5 - name: "twitter:title" 6 content: "Authentication - Goyave" 7 - name: "title" 8 content: "Authentication - Goyave" 9 --- 10 11 # Authentication <Badge text="Since v2.5.0"/> 12 13 [[toc]] 14 15 ## Introduction 16 17 Goyave provides a convenient and expandable way of handling authentication in your application. Authentication can be enabled when registering your routes: 18 19 ``` go 20 import "github.com/System-Glitch/goyave/v2/auth" 21 22 //... 23 24 authenticator := auth.Middleware(&model.User{}, &auth.BasicAuthenticator{}) 25 router.Middleware(authenticator) 26 ``` 27 28 Authentication is handled by a simple middleware calling an **Authenticator**. This middleware also needs a model, which will be used to fetch user information on a successful login. 29 30 #### auth.Middleware 31 32 Middleware create a new authenticator middleware to authenticate the given model using the given authenticator. 33 34 | Parameters | Return | 35 |-------------------------------|---------------------| 36 | `model interface{}` | `goyave.Middleware` | 37 | `authenticator Authenticator` | | 38 39 **Example:** 40 ``` go 41 authenticator := auth.Middleware(&model.User{}, &auth.BasicAuthenticator{}) 42 router.Middleware(authenticator) 43 ``` 44 45 ## Authenticators 46 47 This section will go into more details about Authenticators and explain the built-in ones. You will also learn how to implement an authenticator yourself. 48 49 **`Authenticator`** is a functional interface with a single method accepting a request and a model pointer as parameters. 50 51 ``` go 52 Authenticate(request *goyave.Request, user interface{}) error 53 ``` 54 55 The goal of this function is to check user credentials, most of the time from the request's **headers**. If they are correct and the user can be authenticated, the `user` parameter is updated with the user's information. User information is most of the time fetched from the database. 56 57 On the other hand, if the user cannot be authenticated, the `Authenticate` method must return an `error` containing a localized message. For example, the error could be that the token lifetime is expired, thus "Your authentication token is expired." will be returned. 58 59 Authenticators use their model's struct fields tags to know which field to use for username and password. To make your model compatible with authentication, you must add the `auth:"username"` and `auth:"password"` tags: 60 61 ``` go 62 type User struct { 63 gorm.Model 64 Email string `gorm:"type:varchar(100);unique_index" auth:"username"` 65 Name string `gorm:"type:varchar(100)"` 66 Password string `gorm:"type:varchar(60)" auth:"password"` 67 } 68 ``` 69 70 ::: warning 71 - The username should be **unique**. 72 - Passwords should be **hashed** before being stored in the database. 73 74 Built-in Goyave Authenticators use [`bcrypt`](https://pkg.go.dev/golang.org/x/crypto/bcrypt) to check if a password matches the user request. 75 ::: 76 77 When a user is successfully authenticated on a protected route, its information is available in the controller handler, through the request `User` field. 78 79 ``` go 80 func Hello(response *goyave.Response, request *goyave.Request) { 81 user := request.User.(*model.User) 82 response.String(http.StatusOK, "Hello " + user.Name) 83 } 84 ``` 85 86 ::: tip 87 Remember that Goyave is primarily focused on APIs. It doesn't use session nor cookies in its core features, making requests **stateless**. 88 89 If you want to implement cookie or session-based authentication, be sure to protect your application from [CSRF attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery). 90 ::: 91 92 ### Basic Auth 93 94 [Basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) is an authentication method using the `Authorization` header and a simple username and password combination with the following format: `username:password`, encoded in base64. There are two built-in Authenticators for Basic auth. 95 96 #### Database provider 97 98 This Authenticator fetches the user information from the database, using the field tags explained earlier. 99 100 To apply this protection to your routes, add the following middleware: 101 102 ``` go 103 authenticator := auth.Middleware(&model.User{}, &auth.BasicAuthenticator{}) 104 router.Middleware(authenticator) 105 ``` 106 107 You can then try requesting a protected route: 108 ``` 109 $ curl -u username:password http://localhost:8080/hello 110 Hello Jérémy 111 ``` 112 113 #### Config provider 114 115 This Authenticator fetches the user information from the config. This method is good for quick proof-of-concepts, as it requires minimum setup, but shouldn't be used in real-world applications. 116 117 - The `auth.basic.username` config entry defines the username that must be matched. 118 - The `auth.basic.password` config entry defines the password that must be matched. 119 120 To apply this protection to your routes, start by adding the following content to your configuration: 121 122 ```json 123 { 124 ... 125 "auth": { 126 "basic": { 127 "username": "admin", 128 "password": "admin" 129 } 130 } 131 } 132 ``` 133 134 Then, add the following middleware: 135 136 ``` go 137 router.Middleware(auth.ConfigBasicAuth()) 138 ``` 139 140 The model used for this Authenticator is `auth.BasicUser`: 141 ``` go 142 type BasicUser struct { 143 Name string 144 } 145 ``` 146 147 You can then try requesting a protected route: 148 ``` 149 $ curl -u username:password http://localhost:8080/hello 150 ``` 151 152 #### auth.ConfigBasicAuth 153 154 Create a new authenticator middleware for config-based Basic authentication. On auth success, the request user is set to a `auth.BasicUser`. 155 The user is authenticated if the `auth.basic.username` and `auth.basic.password` config entries match the request's Authorization header. 156 157 | Parameters | Return | 158 |------------|---------------------| 159 | | `goyave.Middleware` | 160 161 ### JSON Web Token (JWT) 162 163 JWT, or [JSON Web Token](https://en.wikipedia.org/wiki/JSON_Web_Token), is an open standard of authentication that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA. Goyave uses HMAC-SHA256 in its implementation. 164 165 JTW Authentication comes with two configuration entries: 166 167 - `auth.jwt.expiry`: the number of seconds a token is valid for. Defaults to `300` (5 minutes). 168 - `auth.jwt.secret`: the secret used for the HMAC signature. This entry **doesn't have a default value**, you need to define it yourself. Use a key that is **at least 256 bits long**. 169 170 To apply JWT protection to your routes, start by adding the following content to your configuration: 171 172 ```json 173 { 174 ... 175 "auth": { 176 "jwt": { 177 "expiry": 300, 178 "secret": "jwt-secret" 179 } 180 } 181 } 182 ``` 183 184 Then, add the following middleware: 185 186 ``` go 187 authenticator := auth.Middleware(&model.User{}, &auth.JWTAuthenticator{}) 188 router.Middleware(authenticator) 189 ``` 190 191 To request a protected route, you will need to add the following header: 192 ``` 193 Authorization: Bearer <YOUR_TOKEN> 194 ``` 195 196 --- 197 198 This Authenticator comes with a built-in login controller for password grant, using the field tags explained earlier. You can register the `/auth/login` route using the helper function `auth.JTWRoutes(router)`. 199 200 #### auth.JWTRoutes 201 202 Create a `/auth` route group and registers the `POST /auth/login` validated route. Returns the new route group. 203 204 Validation rules are as follows: 205 - `username`: required string 206 - `password`: required string 207 208 The given model is used for username and password retrieval and for instantiating an authenticated request's user. 209 210 Ensure that the given router **is not** protected by JWT authentication, otherwise your users wouldn't be able to log in. 211 212 | Parameters | Return | 213 |-------------------------|------------------| 214 | `router *goyave.Router` | `*goyave.Router` | 215 | `model interface{}` | | 216 217 **Example:** 218 ``` go 219 func Register(router *goyave.Router) { 220 auth.JWTRoutes(router, &model.User{}) 221 } 222 ``` 223 224 #### auth.NewJWTController 225 226 If you want or need ot register the routes yourself, you can instantiate a new JWTController using `auth.NewJWTController()`. 227 228 This function creates a new `JWTController` that will be using the given model for login and token generation. 229 230 A `JWTController` contains one handler called `Login`. 231 232 | Parameters | Return | 233 |---------------------|-----------------------| 234 | `model interface{}` | `*auth.JWTController` | 235 236 **Example:** 237 ``` go 238 jwtRouter := router.Subrouter("/auth") 239 jwtRouter.Route("POST", "/login", auth.NewJWTController(&model.User{}).Login).Validate(validation.RuleSet{ 240 "username": {"required", "string"}, 241 "password": {"required", "string"}, 242 }) 243 ``` 244 245 #### auth.GenerateToken 246 247 You may need to generate a token yourself outside of the login route. This function generates a new JWT. 248 249 The token is created using the HMAC SHA256 method and signed using the `auth.jwt.secret` config entry. 250 The token is set to expire in the amount of seconds defined by the `auth.jwt.expiry` config entry. 251 252 The generated token will contain the following claims: 253 - `userid`: has the value of the `id` parameter 254 - `nbf`: "Not before", the current timestamp is used 255 - `exp`: "Expriy", the current timestamp plus the `auth.jwt.expiry` config entry. 256 257 | Parameters | Return | 258 |------------------|----------| 259 | `id interface{}` | `string` | 260 | | `error` | 261 262 **Example:** 263 ``` go 264 token, err := auth.GenerateToken(user.ID) 265 if err != nil { 266 panic(err) 267 } 268 fmt.Println(token) 269 ``` 270 271 ### Writing custom Authenticator 272 273 The Goyave authentication system is expandable, meaning that you can implement more authentication methods by creating a new `Authenticator`. 274 275 The typical `Authenticator` is an empty struct implementing the `Authenticator` interface: 276 ``` go 277 type MyAuthenticator struct{} 278 279 // Ensure you're correctly implementing Authenticator. 280 var _ Authenticator = (*MyAuthenticator)(nil) // implements Authenticator 281 ``` 282 283 The next step is to implement the `Authenticate` method. Its purpose is explained at the start of this guide. 284 285 In this example, we are going to authenticate the user using a simple token stored in the database. 286 ``` go 287 func (a *MyAuthenticator) Authenticate(request *goyave.Request, user interface{}) error { 288 token, ok := request.BearerToken() 289 290 if !ok { 291 return fmt.Errorf(lang.Get(request.Lang, "auth.no-credentials-provided")) 292 } 293 294 // Find the struct field tagged with `auth:"token"` 295 columns := auth.FindColumns(user, "token") 296 297 // Find the user in the database using its token 298 result := database.GetConnection().Where(columns[0].Name+" = ?", token).First(user) 299 300 if errors := result.GetErrors(); len(errors) != 0 && !gorm.IsRecordNotFoundError(result.Error) { 301 // Database error 302 panic(errors) 303 } 304 305 if result.RecordNotFound() { 306 // User not found, return "These credentials don't match our records." 307 return fmt.Errorf(lang.Get(request.Lang, "auth.invalid-credentials")) 308 } 309 310 // Authentication successful 311 return nil 312 } 313 ``` 314 315 #### auth.FindColumns 316 317 Find columns in the given struct. A field matches if it has a "auth" tag with the given value. 318 Returns a slice of found fields, ordered as the input `fields` slice. 319 320 Promoted fields are matched as well. 321 322 If the nth field is not found, the nth value of the returned slice will be `nil`. 323 324 | Parameters | Return | 325 |---------------------|------------------| 326 | `strct interface{}` | `[]*auth.Column` | 327 | `fields ...string` | | 328 329 **Example**: 330 331 Given the following struct and `username`, `notatag`, `password`: 332 333 ``` go 334 type TestUser struct { 335 gorm.Model 336 Name string `gorm:"type:varchar(100)"` 337 Password string `gorm:"type:varchar(100)" auth:"password"` 338 Email string `gorm:"type:varchar(100);unique_index" auth:"username"` 339 } 340 ``` 341 342 ``` go 343 fields := auth.FindColumns(user, "username", "notatag", "password") 344 ``` 345 346 The result will be the `Email` field, `nil` and the `Password` field. 347 348 ::: tip 349 The `Column` struct is defined as follows: 350 ``` go 351 type Column struct { 352 Name string 353 Field *reflect.StructField 354 } 355 ``` 356 ::: 357 358 ## Permissions 359 360 <p style="text-align: center"> 361 <img :src="$withBase('/undraw_in_progress_ql66.svg')" height="150" alt="In progress"> 362 </p> 363 364 ::: warning 365 This feature is not implemented yet and is coming in a future release. 366 367 [Watch](https://github.com/System-Glitch/goyave) the github repository to stay updated. 368 :::