Auth - Authenticator via oauth2

auth - authentication via oauth2, direct and email

Build Status Coverage Status godoc

This library provides "social login" with Github, Google, Facebook, Microsoft, Twitter, Yandex, Battle.net, Apple, Patreon and Telegram as well as custom auth providers and email verification.

  • Multiple oauth2 providers can be used at the same time
  • Special dev provider 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
  • Direct authentication with user's provided credential checker
  • Verified authentication with user's provided sender (email, im, etc)
  • Custom oauth2 server and ability to use any third party provider
  • Integrated avatar proxy with an FS, boltdb and gridfs storage
  • Support of user-defined storage for avatars
  • Identicon for default avatars
  • Black list with user-defined validator
  • Multiple aud (audience) supported
  • Secure key with customizable SecretReader
  • Ability to store an 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
  • Wrappers to extract user info from the request
  • Role based access control

Install

go get -u 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.Minute * 5, // token expires in 5 minutes
		CookieDuration: time.Hour * 24,  // cookie expires in 1 day and will enforce re-login
		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 user
  • middleware.Admin - requires authenticated admin user
  • middleware.Trace - doesn't require authenticated user, but adds user info to request
  • middleware.RBAC - requires authenticated user with passed role(s)

Also, there is a special middleware middleware.UpdateUser for population and modifying UserInfo in every request. See "Customization" for more details.

Details

Generally, adding support of auth includes a few relatively simple steps:

  1. Setup auth.Opts structure with all parameters. Each of them documented and most of parameters are optional and have sane defaults.
  2. Create the new auth.Service with provided options.
  3. Add all desirable authentication providers.
  4. Retrieve middleware and http handlers from auth.Service
  5. 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?site=<site_id>&from=<redirect_url> - site_id used as aud claim for the token and can be processed by SecretReader to 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>/logout and /auth/logout - invalidate "session" by removing JWT cookie
  • /auth/list - gives a json list of active providers
  • /auth/user - returns token.User (json)
  • /auth/status - returns status of logged in 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 name
  • ID - hash of user id
  • Picture - 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 address
  • Email - user's email
  • Attributes - map of string:any-value. To simplify management of this map some setters and getters provided, for example users.StrAttr, user.SetBoolAttr and 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 an 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 the provided stores:
    • avatar.LocalFS - file system, each avatar in a separate file
    • avatar.BoltDB - single boltdb file (embedded KV store).
    • avatar.GridFS - external GridFS (mongo db).
  • In case of need custom implementations of other stores can be passed in and used by auth library. Each store has to implement avatar.Store interface.
  • All avatar-related setup done as a part of auth.Opts and needs:
    • AvatarStore - avatar store to use, i.e. avatar.NewLocalFS("/tmp/avatars") or more generic avatar.NewStore(uri)
      • file system uri - file:///tmp/location or just /tmp/location
      • boltdb - bolt://tmp/avatars.bdb
      • mongo - "mongodb://127.0.0.1:27017/test?ava_db=db1&ava_coll=coll1
    • AvatarRoutePath - route prefix for direct links to proxied avatar. For example /api/v1/avatars will make full links like 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 pixels) used to resize the avatar. Pls note - resize happens once as a part of Put call, i.e. on login. 0 size (default) disables resizing.

Direct authentication

In addition to oauth2 providers auth.Service allows to use direct user-defined authentication. This is done by adding direct provider with auth.AddDirectProvider.

	service.AddDirectProvider("local", provider.CredCheckerFunc(func(user, password string) (ok bool, err error) {
		ok, err := checkUserSomehow(user, password)
		return ok, err
	}))

Such provider acts like any other, i.e. will be registered as /auth/local/login.

The API for this provider supports both GET and POST requests:

  • GET request with user credentials provided as query params:
    GET /auth/<name>/login?user=<user>&passwd=<password>&aud=<site_id>&session=[1|0]
    
  • POST request could be encoded as application/x-www-form-urlencoded or application/json:
    POST /auth/<name>/login?session=[1|0]
    body: application/x-www-form-urlencoded
    user=<user>&passwd=<password>&aud=<site_id>
    
    POST /auth/<name>/login?session=[1|0]
    body: application/json
    {
      "user": "name",
      "passwd": "xyz",
      "aud": "bar",
    }
    

note: password parameter doesn't have to be naked/real password and can be any kind of password hash prepared by caller.

Verified authentication

Another non-oauth2 provider allowing user-confirmed authentication, for example by email or slack or telegram. This is done by adding confirmed provider with auth.AddVerifProvider.

    msgTemplate := "Confirmation email, token: {{.Token}}"
	service.AddVerifProvider("email", msgTemplate, sender)

Message template may use the follow elements:

  • {{.Address}} - user address, for example email
  • {{.User}} - user name
  • {{.Token}} - confirmation token
  • {{.Site}} - site ID

Sender should be provided by end-user and implements a single function interface

type Sender interface {
	Send(address string, text string) error
}

For convenience a functional wrapper SenderFunc provided. Email sender provided in provider/sender package and can be used as Sender.

The API for this provider:

  • GET /auth/<name>/login?user=<user>&address=<adsress>&aud=<site_id>&from=<url> - send confirmation request to user
  • GET /auth/<name>/login?token=<conf.token>&sess=[1|0] - authorize with confirmation token

The provider acts like any other, i.e. will be registered as /auth/email/login.

Telegram

Telegram provider allows your users to log in with Telegram account. First, you will need to create your bot. Contact @BotFather and follow his instructions to create your own bot (call it, for example, "My site auth bot")

Next initialize TelegramHandler with following parameters:

  • ProviderName - Any unique name to distinguish between providers
  • SuccessMsg - Message sent to user on successfull authentication
  • ErrorMsg - Message sent on errors (e.g. login request expired)
  • Telegram - Telegram API implementation. Use provider.NewTelegramAPI with following arguments
    1. The secret token bot father gave you
    2. An http.Client for accessing Telegram API's
token := os.Getenv("TELEGRAM_TOKEN")

telegram := provider.TelegramHandler{
	ProviderName: "telegram",
	ErrorMsg:     "❌ Invalid auth request. Please try clicking link again.",
	SuccessMsg:   "✅ You have successfully authenticated!",
	Telegram:     provider.NewTelegramAPI(token, http.DefaultClient),

	L:            log.Default(),
	TokenService: service.TokenService(),
	AvatarSaver:  service.AvatarProxy(),
}

After that run provider and register it's handlers:

// Run Telegram provider in the background
go func() {
	err := telegram.Run(context.Background())
	if err != nil {
		log.Fatalf("[PANIC] failed to start telegram: %v", err)
	}
}()

// Register Telegram provider
service.AddCustomHandler(&telegram)

Now all your users have to do is click one of the following links and press start tg://resolve?domain=<botname>&start=<token> or https://t.me/<botname>/?start=<token>

Use the following routes to interact with provider:

  1. /auth/<providerName>/login - Obtain auth token. Returns JSON object with bot (bot username) and token (token itself) fields.

  2. /auth/<providerName>/login?token=<token> - Check if auth request has been confirmed (i.e. user pressed start). Sets session cookie and returns user info on success, errors with 404 otherwise.

  3. /auth/<providerName>/logout - Invalidate user session.

Custom oauth2

This provider brings two extra functions:

  1. Adds ability to use any third-party oauth2 providers in addition to the list of directly supported. Included example demonstrates how to do it for bitbucket. In order to add a new oauth2 provider following input is required:
    • Name - any name is allowed except the names from list of supported providers. It is possible to register more than one client for one given oauth2 provider (for example using different names bitbucket_dev and bitbucket_prod)
    • Client - ID and secret of client
    • Endpoint - auth URL and token URL. This information could be obtained from auth2 provider page
    • InfoURL - oauth2 provider API method to read information of logged in user. This method could be found in documentation of oauth2 provider (e.g. for bitbucket https://developer.atlassian.com/bitbucket/api/2/reference/resource/user)
    • MapUserFn - function to convert the response from InfoURL to token.User (s. example below)
    • Scopes - minimal needed scope to read user information. Client should be authorized to these scopes
    c := auth.Client{
    	Cid:     os.Getenv("AEXMPL_BITBUCKET_CID"),
    	Csecret: os.Getenv("AEXMPL_BITBUCKET_CSEC"),
    }
    
    service.AddCustomProvider("bitbucket", c, provider.CustomHandlerOpt{
    	Endpoint: oauth2.Endpoint{
    		AuthURL:  "https://bitbucket.org/site/oauth2/authorize",
    		TokenURL: "https://bitbucket.org/site/oauth2/access_token",
    	},
    	InfoURL: "https://api.bitbucket.org/2.0/user/",
    	MapUserFn: func(data provider.UserData, _ []byte) token.User {
    		userInfo := token.User{
    			ID: "bitbucket_" + token.HashID(sha1.New(),
    				data.Value("username")),
    			Name: data.Value("nickname"),
    		}
    		return userInfo
    	},
    	Scopes: []string{"account"},
    })
  2. Adds local oauth2 server user can fully customize. It uses gopkg.in/oauth2.v3 library and example shows how to initialize the server and setup a provider.
    • to start local oauth2 server following options are required:
      • URL - url of oauth2 server with port
      • WithLoginPage - flag to define whether login page should be shown
      • LoginPageHandler - function to handle login request. If not specified default login page will be shown
      sopts := provider.CustomServerOpt{
      	URL:           "http://127.0.0.1:9096",
      	L:             options.Logger,
      	WithLoginPage: true,
      }
      prov := provider.NewCustomServer(srv, sopts)
      
      // Start server
      go prov.Run(context.Background())
    • to register handler for local oauth2 following option are required:
      • Name - any name except the names from list of supported providers
      • Client - ID and secret of client
      • HandlerOpt - handler options of custom oauth provider
       service.AddCustomProvider("custom123", auth.Client{Cid: "cid", Csecret: "csecret"}, prov.HandlerOpt)

Self-implemented auth handler

Additionally it is possible to implement own auth handler. It may be useful if auth provider does not conform to oauth standard. Self-implemented handler has to implement provider.Provider interface.

// customHandler implements provider.Provider interface
c := customHandler{}

// add customHandler to stack of auth handlers
service.AddCustomHandler(c)

Customization

There are several ways to adjust functionality of the library:

  1. SecretReader - interface with a single method Get(aud string) string to return the secret used for JWT signing and verification
  2. ClaimsUpdater - interface with Update(claims Claims) Claims method. This is the primary way to alter a token at login time and add any attributes, set ip, email, admin status, roles and so on.
  3. Validator - interface with Validate(token string, claims Claims) bool method. This is post-token hook and will be called on each request wrapped with Auth middleware. This will be the place for special logic to reject some tokens or users.
  4. UserUpdater - interface with Update(claims token.User) token.User method. This method will be called on each request wrapped with UpdateUser middleware. This will be the place for special logic modify User Info in request context. Example of usage.

All of the interfaces above have corresponding Func adapters - SecretFunc, ClaimsUpdFunc, ValidatorFunc and UserUpdFunc.

Implementing black list logic or some other filters

Restricting some users or some tokens is two step process:

  • ClaimsUpdater sets an attribute, like blocked (or allowed)
  • Validator checks the attribute and returns true/false

This technique 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.

Multi-tenant services and support for different audiences

For complex systems a single authenticator may serve multiple distinct subsystems or multiple set of independent users. For example some SaaS offerings may need to provide different authentications for different customers and prevent use of tokens/cookies made by another customer.

Such functionality can be implemented in 3 different ways:

  • Different instances of auth.Service each one with different secret. Doing this way will ensure the highest level of isolation and cookies/tokens won't be even parsable across the instances. Practically such architecture can be too complicated and not always possible. – Handling "allowed audience" as a part of ClaimsUpdater and Validator chain. I.e. ClaimsUpdater sets a claim indicating expected audience code/id and Validator making sure it matches. This way a single auth.Service could handle multiple groups of auth tokens and reject some based on the audience.
  • Using the standard JWT aud claim. This method conceptually very similar to the previous one, but done by library internally and consumer don't need to define special ClaimsUpdater and Validator logic.

In order to allow aud support the list of allowed audiences should be passed in as opts.Audiences parameter. Non-empty value will trigger internal checks for token generation (will reject token creation for alien aud) as well as Auth middleware.

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", "", "") or service.AddDevProvider(port) and should be activated like this:

	// runs dev oauth2 server on :8084 by default
	go func() {
		devAuthServer, err := service.DevAuth()
		if err != nil {
			log.Fatal(err)
		}
		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.

By default, Dev provider doesn't return email claim from /user endpoint, to match behaviour of other providers which only request minimal scopes. However sometimes it is useful to have email included into user info. This can be done by configuring devAuthServer.GetEmailFn function:

    go func() {
		devAuthServer, err := service.DevAuth()
		devOauth2Srv.GetEmailFn = func(username string) string {
			return username + "@example.com"
		}
		if err != nil {
			log.Fatal(err)
		}
		devAuthServer.Run()
	}()

Other ways to authenticate

In addition to the primary method (i.e. JWT cookie with XSRF header) there are two more ways to authenticate:

  1. Send JWT header as X-JWT. This shouldn't be used for web application, however can be helpful for service-to-service authentication.
  2. Send JWT token as query parameter, i.e. /something?token=<jwt>
  3. Basic access authentication, for more details see below Basic authentication.

Basic authentication

In some cases the middleware.Authenticator allow use Basic access authentication, which transmits credentials as user-id/password pairs, encoded using Base64 (RFC7235). When basic authentication used, client doesn't get auth token in response. It's auth type expect credentials in a header Authorization at every client request. It can be helpful, if client side not support cookie/token store (e.g. embedded device or custom apps). This mode disabled by default and will be enabled with options.

The auth package has two options of basic authentication:

  • simple basic auth will be enabled if Opts.AdminPasswd defined. This will allow access with basic auth admin:<Opts.AdminPasswd> with user admin. Such method can be used for automation scripts.
  • basic auth with custom checker function, which allow adding user data from store to context of request. It will be enabled if Opts.BasicAuthChecker defined. When BasicAuthChecker defined then Opts.AdminPasswd option will be ignore.
options := auth.Opts{
   //...
   AdminPasswd:    "admin_secret_password", // will ignore if BasicAuthChecker defined
   BasicAuthChecker: func(user, passwd string) (bool, token.User, error) {
      if user == "basic_user" && passwd == "123456" {
           return true, token.User{Name: user, Role: "test_r"}, nil
      }
      return false, token.User{}, errors.New("basic auth credentials check failed")
   }
   //...
}

Logging

By default, this library doesn't print anything to stdout/stderr, however user can pass a logger implementing logger.L interface with a single method Logf(format string, args ...interface{}). Functional adapter for this interface included as logger.Func. There are two predefined implementations in the logger package - NoOp (prints nothing, default) and Std wrapping log.Printf from stdlib.

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

  1. Create a new project: https://console.developers.google.com/project
  2. Choose the new project from the top right project dropdown (only if another project is selected)
  3. In the project Dashboard center pane, choose "API Manager"
  4. In the left Nav pane, choose "Credentials"
  5. In the center pane, choose "OAuth consent screen" tab. Fill in "Product name shown to users" and hit save.
  6. 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"
  7. Take note of the Client ID and Client Secret

instructions for google oauth2 setup borrowed from oauth2_proxy

Microsoft Auth Provider

  1. Register a new application using the Azure portal.
  2. Under "Authentication/Platform configurations/Web" enter the correct url constructed as domain + /auth/microsoft/callback. i.e. https://example.mysite.com/auth/microsoft/callback
  3. In "Overview" take note of the Application (client) ID
  4. Choose the new project from the top right project dropdown (only if another project is selected)
  5. Select "Certificates & secrets" and click on "+ New Client Secret".

GitHub Auth Provider

  1. Create a new "OAuth App": https://github.com/settings/developers
  2. Fill "Application Name" and "Homepage URL" for your site
  3. Under "Authorization callback URL" enter the correct url constructed as domain + /auth/github/callback. ie https://example.mysite.com/auth/github/callback
  4. Take note of the Client ID and Client Secret

Facebook Auth Provider

  1. From https://developers.facebook.com select "My Apps" / "Add a new App"
  2. Set "Display Name" and "Contact email"
  3. Choose "Facebook Login" and then "Web"
  4. Set "Site URL" to your domain, ex: https://example.mysite.com
  5. Under "Facebook login" / "Settings" fill "Valid OAuth redirect URIs" with your callback url constructed as domain + /auth/facebook/callback
  6. Select "App Review" and turn public flag on. This step may ask you to provide a link to your privacy policy.

Apple Auth Provider

To configure this provider, a user requires an Apple developer account (without it setting up a sign in with Apple is impossible). Sign in with Apple lets users log in to your app using their two-factor authentication Apple ID.

Follow to next steps for configuring on the Apple side:

  1. Log in to the developer account.
  2. If you don't have an App ID yet, create one. Later on, you'll need TeamID, which is an "App ID Prefix" value.
  3. Enable the "Sign in with Apple" capability for your App ID in the Certificates, Identifiers & Profiles section.
  4. Create Service ID and bind with App ID from the previous step. Apple will display the description field value to end-users on sign-in. You'll need that service Identifier as a ClientID later on**.**
  5. Configure "Sign in with Apple" for created Service ID. Add domain where you will use that auth on to "Domains and subdomains" and its main page URL (like https://example.com/ to "Return URLs".
  6. Register a New Key (private key) for the "Sign in with Apple" feature and download it. Write down the Key ID. This key will be used to create JWT Client Secret.
  7. Add your domain name and sender email in the Certificates, Identifiers & Profiles >> More section as a new Email Source.

After completing the previous steps, you can proceed with configuring the Apple auth provider. Here are the parameters for AppleConfig:

  • ClientID (required) - Service ID identifier which is used for Sign with Apple
  • TeamID (required) - Identifier a developer account (use as prefix for all App ID)
  • KeyID (required) - Identifier a generated key for Sign with Apple
    // apple config parameters
	appleCfg := provider.AppleConfig{
		TeamID:   os.Getenv("AEXMPL_APPLE_TID"), // developer account identifier
		ClientID: os.Getenv("AEXMPL_APPLE_CID"), // service identifier
		KeyID:    os.Getenv("AEXMPL_APPLE_KEYID"), // private key identifier
	}

Then add an Apple provider that accepts the following parameters:

  • appleConfig (provider.AppleConfig) created above
  • privateKeyLoader (PrivateKeyLoaderInterface)

PrivateKeyLoaderInterface implements a loader for the private key (which you downloaded above) to create a client_secret. The user can use a pre-defined function provider.LoadApplePrivateKeyFromFile(filePath string) to load the private key from local file.

AddAppleProvide tries to load private key at call and return an error if load failed. Always check error when calling this provider.

    if err := service.AddAppleProvider(appleCfg, provider.LoadApplePrivateKeyFromFile("PATH_TO_PRIVATE_KEY_FILE")); err != nil {
		log.Fatalf("[ERROR] failed create to AppleProvider: %v", err)
	}

Limitation:

  • Map a userName (if specific scope defined) is only sent in the upon initial user sign up. Subsequent logins to your app using Sign In with Apple with the same account do not share any user info and will only return a user identifier in IDToken claims. This behaves correctly until a user delete sign in for you service with Apple ID in own Apple account profile (security section). It is recommend that you securely cache the at first login containing the user info for bind it with a user UID at next login. Provider always get user UID (sub claim) in IDToken.

  • Apple doesn't have an API for fetch avatar and user info.

See example before use.

Yandex Auth Provider

  1. Create a new "OAuth App": https://oauth.yandex.com/client/new
  2. Fill "App name" for your site
  3. Under Platforms select "Web services" and enter "Callback URI #1" constructed as domain + /auth/yandex/callback. ie https://example.mysite.com/auth/yandex/callback
  4. 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
  5. Fill out the rest of fields if needed
  6. Take note of the ID and Password

For more details refer to Yandex OAuth and Yandex.Passport API documentation.

Battle.net Auth Provider
  1. Log into Battle.net as a developer: https://develop.battle.net/nav/login-redirect
  2. Click "+ CREATE CLIENT" https://develop.battle.net/access/clients/create
  3. For "Client name", enter whatever you want
  4. For "Redirect URLs", one of the lines must be "http[s]://your_remark_installation:port//auth/battlenet/callback", e.g. https://localhost:8443/auth/battlenet/callback or https://remark.mysite.com/auth/battlenet/callback
  5. For "Service URL", enter the URL to your site or check "I do not have a service URL for this client." checkbox if you don't have any
  6. For "Intended use", describe the application you're developing
  7. Click "Save".
  8. You can see your client ID and client secret at https://develop.battle.net/access/clients by clicking the client you created

For more details refer to Complete Guide of Battle.net OAuth API and Login Button or the official Battle.net OAuth2 guide

Patreon Auth Provider

  1. Create a new Patreon client https://www.patreon.com/portal/registration/register-clients
  2. Fill "App Name", "Description", "App Category" and "Author" for your site
  3. Under "Redirect URIs" enter the correct url constructed as domain + /auth/patreon/callback. ie https://example.mysite.com/auth/patreon/callback
  4. Take note of the Client ID and Client Secret

Twitter Auth Provider

  1. Create a new twitter application https://developer.twitter.com/en/apps
  2. Fill App name and Description and URL of your site
  3. In the field Callback URLs enter the correct url of your callback handler e.g. https://example.mysite.com/{route}/twitter/callback
  4. Under Key and tokens take note of the Consumer API Key and Consumer API Secret key. Those will be used as cid and csecret

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 development and until version 1 released some breaking changes possible.

Owner
go packages
go packages
go packages
Comments
  • Add support of custom oauth2 server (issue#11)

    Add support of custom oauth2 server (issue#11)

    To resolve #11

    I used dev_provider as example for this implementation. Basically I just replaced hardcoded parts of dev_provider with corresponding methods of go-oauth2/oauth2.

  • added sign in with apple

    added sign in with apple

    Implementation of Sign in with Apple #61

    Apple API has some limit for implement full features compatible with the package:

    1. Apple hasn't API for fetch avatar. If any one know how to do it let me know (also no userInfo api).

    2. Fetch user name (if set in scope) possible only one, at first user login with a service. With next login userName field doesn't exist in API response, until user will delete sign with an app in account profile (security section).

    I will planning update the Readme after code review.

    Waiting for your comments

  • Bug: auth should also check if xsrf token is set in cookies after checking via headers

    Bug: auth should also check if xsrf token is set in cookies after checking via headers

    https://github.com/go-pkgz/auth/blob/5784dad303c1c1937d694a19f52a878caf5e863b/token/jwt.go#L302

    this line seems to suggest that only the header value (default X-XSRF-TOKEN) is checked to see if the token is there.

    if the user has not set that header then surely the default cookie value defaultXSRFCookieName = "XSRF-TOKEN" should be then checked to see if it set and used?

  • Example frontend

    Example frontend

    Made simple page: screenshot

    I decided to show private_page as just an iframe, so it doesn't duplicate user info.

    I have already said that we have trouble with token/auth naming issue. Made an issue #4

    Opened issue #2

    Also, I decided not to store XSRF-TOKEN cookie anywhere, I think it has no point for example, what do you think?

  • Implement Telegram provider

    Implement Telegram provider

    This PR adds Telegram as a login provider. The new flow uses deep-linking (see official example) to establish user identity. Here are the steps:

    1. User clicks link that opens up chat with login bot in local Telegram client
    2. User presses 'Start' button which silently sends us some well-known value
    3. The page magically logs user in (thanks to client-side polling)

    TODO:

    • [x] Tests
    • [x] Downloading user avatars
    • [x] Auth request eviction

    Closes #49

  • fix iframe xsrf header missing

    fix iframe xsrf header missing

    Fix for #8

    Fix a bit hacky, because there is no way to send headers with an iframe. Have to make ajax call, then create object URL from received data and set url of an iframe to said object URL.

  • Unable to access XSRF cookie from chrome plugin

    Unable to access XSRF cookie from chrome plugin

    I have a chrome extension as frontend for my application. I am unable to get XSRF-TOKEN cookie from this extension.

    It works fine, if I host a web frontend on same domain with my backend server. But if I try to do the same from chrome plugin, it's not able to access the cookie. Maybe it's because of some cross site protection? Do we have any flag to disable such kind of protection?

  • Add Telegram login

    Add Telegram login

    Fixes #49

    I used rather different approach than you describe in the issue. I tried to implement telegram login to be very close to implementation of other oauth2 providers. Main differences between oauth2 and telegram provider are:

    1. In oauth2 providers login handler redirect client to login page. In telegram provider login handler renders template with iframe with telegram button
    2. state is checked on integrity in oauth2 providers between login and auth handler. In telegram provider there is custom check of hash with so-called data-check-string

    This is working version that I successfully tested with _example/main.go. But I haven't added tests, documentation, comments yet. I'd like to hear your opinion before I make final pull request.

  • Auth/Token naming mismatch

    Auth/Token naming mismatch

    Noticed, that although readme tells to use {website}/auth/{prov}/callback, the code uses {website}/token/{prov}/callback judging by https://github.com/go-pkgz/auth/blob/master/provider/providers.go and logs.

  • Authentication cookies not readable on localhost

    Authentication cookies not readable on localhost

    I have hosted my go based direct oauth login server at localhost:9999

    My frontend server is running at localhost:3000, I am able to hit /oauth/local/login api from my browser successfully, but it doesn't populate cookies in any further requests.

    I tried lots of solutions on google, most of them are pointing to enable credentials in header. I did try to set these paramerters in my header response:

    		responseWriter.Header().Set("Access-Control-Expose-Headers", "XSRF-TOKEN")
    		responseWriter.Header().Set("Access-Control-Allow-Origin", "localhost:9999, localhost, localhost:3000")
    		responseWriter.Header().Set("Access-Control-Allow-Credentials", "true")
    

    But it still doesn't work. Any idea on what I am missing here? Do I have to enable some flag in auth library?

    PS: It's working fine if I serve frontend files from my go server directly.

  • support both personal and work/school Microsoft accounts

    support both personal and work/school Microsoft accounts

    The tenant for microsoft.AzureADEndpoint is currently "consumers". As per the documentation, this allows only users with personal Microsoft accounts (MSA) to sign into the application. Change it to "common" in order to allow users with both personal Microsoft accounts and work/school accounts from Azure AD to sign into the application.

    Fixes #106

  • Bug: (/_example) Using

    Bug: (/_example) Using "Login with custom_123" results in "Invalid Request"

    Thanks for all the great work on this library.

    This is my first issue, so apologies if I've missed the mark or omitted something. Constructive criticism is appreciated.

    Version: master v1.18.0 Browser: Chrome Version 106.0.5249.103 (Official Build) (64-bit)

    Problem: When running the example and choosing "Login with custom_123", entering the credentials provided results in an invalid request.

    Steps:

    • git clone https://github.com/go-pkgz/auth/
    • cd /auth/_example
    • go mod tidy
    • go run main.go
    • open http://localhost:8080/web/ in a browser
    • click Login with custom123
    • enter admin as username
    • enter adminpassword
    • click authorize

    Expectation:

    You are redirected to http://localhost:8080/web/ and are authorized using the custom provider

    Result:

    "Invalid Request"

    Notes:

    Replacing the action attribute of the form element present on http://127.0.0.1:9096/authorize.. with a URL decoded version results in success.

    This is what the

    element looks like when inspecting http://127.0.0.1:9096/authorize... (the custom oauth2 server)

    <form action="/login/oauth/authorize?client_id%3dcid%26redirect_uri%3dhttp%253A%252F%252Flocalhost%253A8080%252Fauth%252Fcustom123%252Fcallback%26response_type%3dcode%26state%3dd11e3e82d573eb897c28e8ed47f5798c791e15b5" method="POST">

    If I edit the action attribute and and decode it via https://meyerweb.com/eric/tools/dencoder/

    I get the following:

    /login/oauth/authorize?client_id=cid&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fauth%2Fcustom123%2Fcallback&response_type=code&state=d11e3e82d573eb897c28e8ed47f5798c791e15b5

    If I replace the original form action with the above then the form submission works as expected.

  • Give an access to bearer token, aquired during authentication proccess

    Give an access to bearer token, aquired during authentication proccess

    In provider/oauth2 the AuthHandler func does receive a brearer-token, using which we could pontentially access a private API of identity provider. Wouldn't it be useful? Any concerns about adding some hook for this? That way go-pkgz/auth will be used as an authorization framework, allowing to setup a proxy to a private API without giving a client any direct access to bearer-token itself.

  • Support OpenID tokens for getting using information

    Support OpenID tokens for getting using information

    Some providers (e.g. Cognito) provide only minimal subset of user attributes in /userinfo endpoint, but much more in the ID tokens. That can be helpful to pass user metadata from the provider to the app. This PR adds support of OpenID tokens as a source of user info, as an extension to the OAuth2 flow.

    OpenID flow is only supported for custom providers at the moment and require JWKS URL configuration. Switching existing providers (e.g. google) to OpenID is TBD, but probably not required. Using OpenID Connect (.well-known/openid-configuration URLs) is out of scope for this PR.

    Apple flow basically is OpenID, with some customisations on how client secret is passed across. So potentially these two flows can be merged together later on.

  •  twitter oauth now required to use oauth2 - unless you have elevated access

    twitter oauth now required to use oauth2 - unless you have elevated access

    As per this statement on twitter's developer website (https://developer.twitter.com/en/docs/twitter-api/getting-started/about-twitter-api):

    If you were approved for a developer account before November 15th, 2021, you were automatically converted to Elevated access. This means that your existing Apps can continue to be used to make requests to standard v1.1, premium v1.1, and enterprise endpoints, and that all of your user Access Tokens are still valid.

    If you would like to start using v2 endpoints, you will need to attach an App to a Project and use the credentials from that App when making requests to v2 endpoints.

    New users are locked in to using oauth2 unless they apply for elevated access, which puts you on a wait list. This conflicts with the existing oauth1 implementation for twitter and I feel that the best way to go forward is to add an oauth2 method for it along with oauth1 so that things don't break for existing users, but allow new users the ability to use twitter easily.

    It looks like @nbys was the one who did this pr for adding oauth1 implementation - how easy would it be to add in oauth2? I'm not super familiar with go but I could probably take a stab at it myself, I'm just not sure how to handle the whole "allow both oauth1 and oauth2" debacle.

  • Support OpenID and 3rd party IdTokens with cookie-less authentication

    Support OpenID and 3rd party IdTokens with cookie-less authentication

    There are probably quite a few parts in this issue, I totally see those as separate small PRs / issues.

    Context

    Currently, auth package supports various OAuth2 providers and acts as a OAuth2 Authorization Server / proxy (without strongly committing to RFC) by issuing self-signed ID tokens based on info provided by /userinfo response, which are passed via a cookie, with extra XSRF protection.

    Proposal

    Support OpenID providers and ID tokens issued by an external identity provider as authentication tokens. In this scenario, the server won't have private keys and won't issue ID tokens itself. Potentially support OpenID Connect Discovery protocol. Unlike general OAuth2 spec, OpenID supports issuing signed ID tokens bound to a particular user, so these tokens can be used for authentication.

    How I see the potential configuration:

        // code below will fetch OpenID Connect Discovery config 
        // from https://accounts.google.com/.well-known/openid-configuration
        // then it'll load keys from "jwks_uri" endpoint and store them under "google" provider
        service.AddOpenIDProvider("google", "https://accounts.google.com", "client-id", "client-secret") 
        
        m := service.Middleware()
        router := chi.NewRouter()
    
        // setup auth routes
        authRoutes, avaRoutes := service.Handlers()
    
        // for OpenID providers, /auth handler won't just fetch access token + userinfo, 
        // but will actually request a signed ID token from the provider. 
        // This token can be then passed to the client as is, as it is signed already 
        // and we have public keys for validate it.
        router.Mount("/auth", authRoutes)  // add auth handlers
        router.Mount("/avatar", avaRoutes) // add avatar handler
    
    
        // m.OpenIDAuth won't use own keys defined in opts.SecretReader, but instead will use public keys loaded before
        // the provider can be identified either by "iss" claim or manually configured, i.e. m.OpenIDAuth("google")
    
        // Also, OpenIDAuth shouldn't require a cookie, but Authorization: Bearer <jwt> header to work.
        router.With(m.OpenIDAuth).Get("/api-endpoint", protectedRouteHandler) // protected api
    
        router.With(m.OpenIDAuth(func(claims token.Claims) bool {
            // do any authorization checks on the token claims, e.g. Role 
        })).Get("/api-endpoint", protectedRouteHandler) // protected api
    

    With OpenIDAuth authentication middleware, the server should act as a OAuth2 Resource Server, i.e. to expect Authorization: Bearer <jwt> header and use that for authentication.

    Having this would allow:

    • Remove need in issuing auth (ID) tokens, as well as managing signing keys
    • Easy integration with new providers
    • Unify user / API authentication middleware (by using Authorization header)
    • Multiple services using the same auth middleware configuration without sharing secrets

    Considerations:

    • Some parts of configuration options are irrelevant for OpenID providers, where auth is only acting as a authentication proxy basically. Like opts.ClaimsUpdater won't work - since we don't have private keys, we can't modify claims. Probably OpenID should have own configuration options?
    • How to pass a signed ID token to the client? I think the cookie approach can still be used, but instead of checking the cookie, Authorization header should be checked, which is set from the cookie. This kind of gives XSRF protection for free.
Hazelcast Storage for go-oauth2/oauth2

Hazelcast Storage for go-oauth2/oauth2

Jan 26, 2022
vault-plugin-auth-usertotp is an auth method plugin for HashiCorp Vault

vault-plugin-auth-usertotp is an auth method plugin for HashiCorp Vault. Create user accounts, add TOTP tokens (user supplied pin + totp), and have peace of mind using 2FA.

Jul 5, 2021
vault-plugin-auth-usertotp is an auth method plugin for HashiCorp Vault.

vault-plugin-auth-usertotp is an auth method plugin for HashiCorp Vault. Create user accounts, add TOTP tokens (user supplied pin + totp), and have peace of mind using 2FA.

Jul 30, 2021
Gets Firebase auth tokens (for development purposes only)Gets Firebase auth tokens

Firebase Token Gets Firebase auth tokens (for development purposes only) Getting started Create Firebase project Setup Firebase authentication Setup G

Nov 17, 2021
Google Authenticator for Go

This is a Go implementation of the Google Authenticator library. Copyright (c) 2012 Damian Gryski [email protected] This code is licensed under the Ap

Dec 31, 2022
Oct 8, 2022
A tool to manage accounts and codes of Google Authenticator.

A tool to manage accounts and codes of Google Authenticator.

Sep 10, 2021
Go library for one-time passwords, supports HOPT and TOPT (Google Authenticator compatible)

GoTP: One-time password library for Go GoTP library provides implementations of one-time password generators and validators. This implemantation suppo

Oct 5, 2022
Go based HTTP server with 2FA based on OTP (One-Time Password) manager like Google Authenticator

Go based HTTP server with 2FA based on OTP (One-Time Password) manager like Goog

Aug 21, 2022
Generate a generic library of 2FA tokens compatible with Google Authenticator

towfa Generate a generic library of 2FA tokens compatible with Google Authenticator go get -u github.com/golandscape/twofa $twofa "you secret" result:

Mar 23, 2022
A standalone, specification-compliant, OAuth2 server written in Golang.
A standalone, specification-compliant,  OAuth2 server written in Golang.

Go OAuth2 Server This service implements OAuth 2.0 specification. Excerpts from the specification are included in this README file to describe differe

Dec 28, 2022
Go login handlers for authentication providers (OAuth1, OAuth2)
Go login handlers for authentication providers (OAuth1, OAuth2)

gologin Package gologin provides chainable login http.Handler's for Google, Github, Twitter, Facebook, Bitbucket, Tumblr, or any OAuth1 or OAuth2 auth

Dec 30, 2022
JWT login microservice with plugable backends such as OAuth2, Google, Github, htpasswd, osiam, ..
JWT login microservice with plugable backends such as OAuth2, Google, Github, htpasswd, osiam, ..

loginsrv loginsrv is a standalone minimalistic login server providing a JWT login for multiple login backends. ** Attention: Update to v1.3.0 for Goog

Dec 24, 2022
Go OAuth2

OAuth2 for Go oauth2 package contains a client implementation for OAuth 2.0 spec. Installation go get golang.org/x/oauth2 Or you can manually git clo

Jan 8, 2023
Golang OAuth2 server library

OSIN Golang OAuth2 server library OSIN is an OAuth2 server library for the Go language, as specified at http://tools.ietf.org/html/rfc6749 and http://

Dec 23, 2022
A Sample Integration of Google and GitHub OAuth2 in Golang (GoFiber) utilising MongoDB

Go Oauth Server This is sample OAuth integration written in GoLang that also uses MongoDB. This is a sample TODO Application where people can Create a

Dec 27, 2022
Go library providing in-memory implementation of an OAuth2 Authorization Server / OpenID Provider

dispans Go library providing in-memory implementation of an OAuth2 Authorization Server / OpenID Provider. The name comes from the Swedish word dispen

Dec 22, 2021
Envoy Oauth2 Filter helloworld
Envoy Oauth2 Filter helloworld

Envoy Oauth2 Filter A simple sample demonstrating Envoy's Oauth2 Filter. Basically, this filter will handle all the details for OAuth 2.0 for Web Serv

Jan 2, 2023
Identity-service - An OAuth2 identity provider that operates over gRPC

Identity-service - An OAuth2 identity provider that operates over gRPC

May 2, 2022