oauth

package module
v0.0.0-...-c5b85a4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2024 License: BSD-2-Clause Imports: 14 Imported by: 0

README

Go OAuth

This is work-in-progress.

This simple package supports OAuth Authorization flow. It is very simple to use and very small. If you're looking for a robust OAuth package with bells and whistles, this is not it. I wrote it because I was curious to learn more about OAuth and I also found others to be overly complex. This does what I need and works well with Tradier Brokerage (I have not tested with other providers).

To use, first obtain a client ID and a client secret from an OAuth provider. Set the callback uri you want to support (what your server will answer when the provider calls) with the provider. Then get their two endpoints, authorize URL and token URL. Then:

import (
    "fmt"
    "net/http"

    "github.com/edpin/https"
    "github.com/edpin/oauth"
)

const (
    oauthBase    = "https://oauth.example.com/"
    providerName = "Some OAuth Provider"
    clientID     = "..."
    clientSecret = "..."
)

func main() {
   auth = oauth.New(providerName, clientID, clientSecret,
                    oauthBase+"oauth/authorize", oauthBase+"oauth/accesstoken",
                    oauthBase+"oauth/refreshtoken")

   mux := http.NewServeMux()
   mux.Handle("/auth/callback", auth)
   mux.HanldeFunc("/login", func(w http.ResponseWriter, r *http.Request) {
	    const loginForm = `
<html>
<body>
<a href="%s">Login</a>
</body>
</html>
`
    	user := oauth.UserName(r.RemoteAddr)  // Or pick a better UserName.
    	url, err := auth.AuthorizeURL(user, []string{"read"}, "/logged")
    	if err != nil {
	        w.WriteHeader(http.StatusInternalServerError)
		    w.Write([]byte(fmt.Sprintf("Something went wrong: %s", err)))
		    return
	    }
	    w.Write([]byte(fmt.Sprintf(loginForm, url)))
})

    // Consume all login notifications.
    go func(notifications chan *oauth.User) {
        for {
            user := <- notifications
            log.Printf("User %s authorized.", user.UserName)
        }
    }(auth.Login)

    // Start your server or use github.com/edpin/https:
    https.StartSecureServer(mux, nil)
}

To deal with refreshes, if your provided returned a RefreshToken for your users, simply call user.RefreshAccessTokenIfNeeded() before each time you need an access token for that user.

TODOs:

  1. Use a small pool of HTTPS clients instead of creating a new one every time.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("Username not found")

Functions

This section is empty.

Types

type OAuth

type OAuth struct {
	// ProviderName is the name of the OAuth provider. It's set at creation
	// time and its purpose is purely to identify the provider, without
	// having to inspect the URL endpoints. It is not used internally.
	ProviderName string

	// Login is a buffered channel that receives notifications when new auth
	// requests succeed. If the buffer fills up, further notifications are
	// dropped until the channel has buffer space again.
	Login chan *User
	// contains filtered or unexported fields
}

OAuth is an HTTP.Handler that handles the OAuth dance.

func New

func New(providerName, clientID, clientSecret, authorizeURL, tokenURL, refreshURL string) *OAuth

New creates an OAuth client for a providerName with a clientID and clientSecret as given. The endpoint for retrieving an authorization code is given by the authorizeURL. The endpoint for retrieving an access token is given by the tokenURL. The endpoint for refreshing access tokens is given by refreshURL, which is optional. All provided endpoints must be fully specified (i.e. the full URL starting with "https://"). The provider name is optional.

func (*OAuth) AuthorizeURL

func (o *OAuth) AuthorizeURL(u UserName, scopes []string, nextURL string) (*url.URL, error)

AuthorizeURL returns a URL that can be rendered for users to authenticate themselves and obtain authorization for the scopes listed. An opaque user name or ID is given so this server can associate the user with the authorized key. Once authorized, the user is redirected to the nextURL (if it's empty, the user will see a debug message).

func (*OAuth) Load

func (o *OAuth) Load(u *User) (bool, error)

Load loads a user. If the user is already known, all fields are updated with the new contents, otherwise it's inserted. No effort is made in validating the fields, except for UserName, which must not be empty. Upon successful return, load reports whether the new field was updated (true) or newly inserted (false).

func (*OAuth) RegisterCallbackHandler

func (o *OAuth) RegisterCallbackHandler(pattern string, httpServer *http.ServeMux)

RegisterCallbackHandler is a convenience function that associates a given access pattern on the given HTTP server with the registration callback. This pattern must be the same one registered with the OAuth service provider for this application. For example, "/auth", "/auth/callback". The HTTP server parameter may be nil, in which case the pattern is registered with the default HTTP server.

func (*OAuth) ServeHTTP

func (o *OAuth) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler. It handles the callback response from the OAuth provider as registered by RegisterCallbackHandler. This should not be called directly; it is used by the HTTP server.

func (*OAuth) SetErrorTemplate

func (o *OAuth) SetErrorTemplate(errTpl *template.Template)

SetErrorTemplate sets a template for returning error pages to the user when the registered callback is called with the wrong parameters. This is only relevant in a few cases: 1) if you expect someone will accidentally visit the callback URL; 2) if the user declines to authorize and hence can't continue to nextURL (set by AuthorizeURL); 3) the provider makes an invalid call or a very delayed call and there's no longer a context for the user.

Setting an error template is optional. A simple one is provided by default.

The error template can make use of two fields ".ErrorMsg" for a short error message and ".ErrorCode" which is the HTTP error code (int) being returned. The error template can only be set before registering OAuth with an HTTP server (i.e. before using it for the first time). The template must not be changed afterwards (Clone it first if subsequent changes are planned).

func (*OAuth) User

func (o *OAuth) User(u UserName) (*User, error)

User returns the known facts about the given user or ErrNotFound if the user is not known or has dropped off the cache.

type User

type User struct {
	UserName     UserName  // User's name.
	AccessToken  string    // User's access token.
	RefreshToken string    // Token to refresh the access token.
	Expiration   time.Time // Time the access token expires, in UTC.
	Scopes       []string  // Scopes granted to the access token.
	// contains filtered or unexported fields
}

User represents a user and holds the UserName and AccessToken.

func (*User) RefreshAccessToken

func (u *User) RefreshAccessToken() error

RefreshAccessToken makes a network call to refresh the access token, if this user has a refresh token.

func (*User) RefreshAccessTokenIfNeeded

func (u *User) RefreshAccessTokenIfNeeded() error

RefreshAccessTokenIfNeeded refreshes the access token if the user has a refresh token and the access token is close to expiring. This may do a network request. Upon successful return, a call to User is guaranteed to yield the refreshed token. It does not generate an event on the login channel.

type UserName

type UserName string

UserName is a string that identifies a user. It's opaque to this package and is only used for associating an access token with some account. Examples of useful user names are email addresses, user IDs, actual user names, etc.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL