12 KiB
auth - authentication via oauth2

This library provides "social login" with Github, Google, Facebook and Yandex.
- Multiple oauth2 providers can be used at the same time
- Special
devprovider allows local testing and development - JWT stored in a secure cookie with XSRF protection. Cookies can be session-only
- Minimal scopes with user name, id and picture (avatar) only
- Integrated avatar proxy with FS, boltdb and gridfs storages
- Support of user-defined storages for avatars
- Black list with user-defined validator
- Multiple aud (audience) supported
- Secure key with customizable
SecretReader - Ability to store extra information to token and retrieve on login
- Pre-auth and post-auth hooks to handle custom use cases.
- Middleware for easy integration into http routers
Install
go install github.com/go-pkgz/auth
Usage
Example with chi router:
func main() {
/// define options
options := auth.Opts{
SecretReader: token.SecretFunc(func(id string) (string, error) { // secret key for JWT
return "secret", nil
}),
TokenDuration: time.Hour,
CookieDuration: time.Hour * 24,
Issuer: "my-test-app",
URL: "http://127.0.0.1:8080",
AvatarStore: avatar.NewLocalFS("/tmp"),
Validator: token.ValidatorFunc(func(_ string, claims token.Claims) bool {
// allow only dev_* names
return claims.User != nil && strings.HasPrefix(claims.User.Name, "dev_")
}),
}
// create auth service with providers
service := auth.NewService(options)
service.AddProvider("github", "<Client ID>", "<Client Secret>") // add github provider
service.AddProvider("facebook", "<Client ID>", "<Client Secret>") // add facebook provider
// retrieve auth middleware
m := service.Middleware()
// setup http server
router := chi.NewRouter()
router.Get("/open", openRouteHandler) // open api
router.With(m.Auth).Get("/private", protectedRouteHandler) // protected api
// setup auth routes
authRoutes, avaRoutes := service.Handlers()
router.Mount("/auth", authRoutes) // add auth handlers
router.Mount("/avatar", avaRoutes) // add avatar handler
log.Fatal(http.ListenAndServe(":8080", router))
}
Middleware
github.com/go-pkgz/auth/middleware provides ready-to-use middleware.
middleware.Auth- requires authenticated usermiddleware.Admin- requires authenticated and admin usermiddleware.Trace- doesn't require authenticated user, but adds user info to request
Details
Generally, adding support of auth includes a few relatively simple steps:
- Setup
auth.Optsstructure with all parameters. Each of them documented and most of parameters are optional and have sane defaults. - Create the new
auth.Servicewith provided options. - Add all desirable authentication providers. Currently supported Github, Google, Facebook and Yandex
- Retrieve middleware and http handlers from
auth.Service - Wire auth and avatar handlers into http router as sub–routes.
API
For the example above authentication handlers wired as /auth and provides:
/auth/<provider>/login?id=<site_id>&from=<redirect_url>- site_id used asaudclaim for the token and can be processed bySecretReaderto load/retrieve/define different secrets. redirect_url is the url to redirect after successful login./avatar/<avatar_id>- returns the avatar (image). Links to those pictures added into user info automatically, for details see "Avatar proxy"/auth/<provider>/logoutand/auth/logout- invalidate "session" by removing JWT cookie/auth/list- gives a json list of active providers/auth/user- returnstoken.User(json)
User info
Middleware populates token.User to request's context. It can be loaded with token.GetUserInfo(r *http.Request) (user User, err error) or token.MustGetUserInfo(r *http.Request) User functions.
token.User object includes all fields retrieved from oauth2 provider:
Name- user nameID- hash of user idPicture- full link to proxied avatar (see "Avatar proxy")
It also has placeholders for fields application can populate with custom token.ClaimsUpdater (see "Customization")
IP- hash of user's IP addressEmail- user's emailAttributes- map of string:any-value. To simplify management of this map some setters and getters provides, for exampleusers.StrAttr,user.SetBoolAttrand so on. See user.go for more details.
Avatar proxy
Direct links to avatars won't survive any real-life usage if they linked from a public page. For example, page like this may have hundreds of avatars and, most likely, will trigger throttling on provider's side. To eliminate such restriction auth library provides and automatic proxy
- On each login the proxy will retrieve user's picture and save it to
AvatarStore - Local (proxied) link to avatar included in user's info (jwt token)
- API for avatar removal provided as a part of
AvatarStore - User can leverage one of provided stores:
- In case of need a custom implementation of other stores can be passed in and used by
authlibrary. Each store has to implementavatar.Storeinterface. - All avatar-related setup done as a part of
auth.Optsand needs:AvatarStore- avatar store to use, i.e.avatar.NewLocalFS("/tmp/avatars")AvatarRoutePath- route prefix for direct links to proxied avatar. For example/api/v1/avatarswill make full links links this -http://example.com/api/v1/avatars/1234567890123.image. The url will be stored in user's token and retrieved by middleware (see "User Info")AvatarResizeLimit- size (in pixel) used to resize avatar. Pls note - resize happens once as a part ofPutcall, i.e. on login. 0 size (default) disables resizing.
Customization
There are several ways to adjust functionality of the library:
SecretReader- interface with a single methodGet(aud string) stringto return secret used for JWT signing and verificationClaimsUpdater- interface withUpdate(claims Claims) Claimsmethod. This is the primary way to alter a token at login time and add any attributes, set ip, email, admin status and so on.Validator- interface withValidate(token string, claims Claims) boolmethod. This is post-token hook and will be called on each request wrapped withAuthmiddleware. This will be the place for special logic to reject some tokens or users.
All of interfaces have corresponding Func wrappers (adapters) - SecretFunc, ClaimsUpdFunc and ValidatorFunc.
Implementing black list logic or some other filters
Restricting some users or some tokens is two step process:
ClaimsUpdatersets an attribute, likeblocked(orallowed)Validatorchecks the attribute and returns true/false
This technic used in the example code
The process can be simplified by doing all checks directly in Validator, but depends on particular case such solution
can be too expensive because Validator runs on each request as a part of auth middleware. In contrast, ClaimsUpdater called on token creation/refresh only.
Dev provider
Working with oauth2 providers can be a pain, especially during development phase. A special, development-only provider dev can make it less painful. This one can be registered directly, i.e. service.AddProvider("dev", "", "") and should be activated like this:
// runs dev oauth2 server on :8084
go func() {
p, err := service.Provider("dev")
if err != nil {
log.Fatal(err)
}
devAuthServer := provider.DevAuthServer{Provider: p}
devAuthServer.Run()
}()
It will run fake aouth2 "server" on port :8084 and user could login with any user name. See example for more details.
Warning: this is not the real oauth2 server but just a small fake thing for development and testing only. Don't use dev provider with any production code.
Register oauth2 providers
Authentication handled by external providers. You should setup oauth2 for all (or some) of them to allow users to authenticate. It is not mandatory to have all of them, but at least one should be correctly configured.
Google Auth Provider
- Create a new project: https://console.developers.google.com/project
- Choose the new project from the top right project dropdown (only if another project is selected)
- In the project Dashboard center pane, choose "API Manager"
- In the left Nav pane, choose "Credentials"
- In the center pane, choose "OAuth consent screen" tab. Fill in "Product name shown to users" and hit save.
- In the center pane, choose "Credentials" tab.
- Open the "New credentials" drop down
- Choose "OAuth client ID"
- Choose "Web application"
- Application name is freeform, choose something appropriate
- Authorized origins is your domain ex:
https://example.mysite.com - Authorized redirect URIs is the location of oauth2/callback constructed as domain +
/auth/google/callback, ex:https://example.mysite.com/auth/google/callback - Choose "Create"
- Take note of the Client ID and Client Secret
instructions for google oauth2 setup borrowed from oauth2_proxy
GitHub Auth Provider
- Create a new "OAuth App": https://github.com/settings/developers
- Fill "Application Name" and "Homepage URL" for your site
- Under "Authorization callback URL" enter the correct url constructed as domain +
/auth/github/callback. iehttps://example.mysite.com/auth/github/callback - Take note of the Client ID and Client Secret
Facebook Auth Provider
- From https://developers.facebook.com select "My Apps" / "Add a new App"
- Set "Display Name" and "Contact email"
- Choose "Facebook Login" and then "Web"
- Set "Site URL" to your domain, ex:
https://example.mysite.com - Under "Facebook login" / "Settings" fill "Valid OAuth redirect URIs" with your callback url constructed as domain +
/auth/facebook/callback - Select "App Review" and turn public flag on. This step may ask you to provide a link to your privacy policy.
Yandex Auth Provider
- Create a new "OAuth App": https://oauth.yandex.com/client/new
- Fill "App name" for your site
- Under Platforms select "Web services" and enter "Callback URI #1" constructed as domain +
/auth/yandex/callback. iehttps://example.mysite.com/auth/yandex/callback - Select Permissions. You need following permissions only from the "Yandex.Passport API" section:
- Access to user avatar
- Access to username, first name and surname, gender
- Fill out the rest of fields if needed
- Take note of the ID and Password
For more details refer to Yandex OAuth and Yandex.Passport API documentation.
Status
The library extracted from remark42 project. The original code in production use on multiple sites and seems to work fine.
go-pkgz/auth library still in beta and until version 1 released some breaking changes still possible.