CLI tool
dghubble/gologin avatar
dghubble/gologin

gologin: chained http.Handlers for OAuth1 and OAuth2 login flows in Go

Go login handlers for authentication providers (OAuth1, OAuth2)

1,957 stars147 forksGoMIT

At a glance

What is it?
gologin gives you LoginHandler, CallbackHandler and TokenHandler values that compose into an OAuth flow and pass the result through the request context. The chaining model is the design, and the CSRF state cookie defaults are the detail most deployments get wrong on the way to production.
Who is it for?
Adopt gologin when you serve Go's net/http and want a provider-specific user object without writing the OAuth dance per provider, and use `gologin.DefaultCookieConfig` rather than `gologin.DebugOnlyCookieConfig` the moment you are not on localhost. Do not adopt it for OpenID Connect, SAML or passkey flows, none of which appear in the package list, nor if you would rather have a single framework that owns your routes and sessions.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Login flows are middleware you chain, not a struct you configure

gologin's core idea is that an authorization flow is a chain of http.Handler values. Each handler takes two next handlers, a success handler and a failure handler, and calls one of them when the flow succeeds or fails. Data travels along the chain through the request context.

The result is that you register two or three routes and read the outcome from the context. The README describes this as chainable login http.Handlers, and notes that chaining allows advanced customization if desired, which is the extension point rather than a configuration struct.

The provider layer sits above a protocol layer. A provider subpackage such as github or twitter exposes LoginHandler and CallbackHandler, and those in turn wrap lower level oauth1 or oauth2 handlers. The protocol handlers complete the flow, and the provider handler uses the resulting token to fetch a provider User before calling your handler. Choosing a subpackage is the first decision the package asks of you.

GitHub OAuth2 in three registrations and one success handler

The GitHub example wires an oauth2.Config from golang.org/x/oauth2 with the provider endpoint, then registers two routes on a mux. Both routes are wrapped in a StateHandler built from a cookie config:

go
config := &oauth2.Config{
    ClientID:     "GithubClientID",
    ClientSecret: "GithubClientSecret",
    RedirectURL:  "http://localhost:8080/callback",
    Endpoint:     githubOAuth2.Endpoint,
}
mux := http.NewServeMux()
stateConfig := gologin.DebugOnlyCookieConfig
mux.Handle("/login", github.StateHandler(stateConfig, github.LoginHandler(config, nil)))
mux.Handle("/callback", github.StateHandler(stateConfig, github.CallbackHandler(config, issueSession(), nil)))

Passing nil as the failure handler is not a bug in the example. It selects the DefaultFailureHandler, which reports the error.

The success handler then reads both the OAuth2 token and the GitHub user out of the request context:

go
func issueSession() http.Handler {
    fn := func(w http.ResponseWriter, req *http.Request) {
        ctx := req.Context()
        token, _ := oauth2Login.TokenFromContext(ctx)
        githubUser, err := github.UserFromContext(ctx)
        // handle errors and grant the visitor a session (cookie, token, etc.)
    }
    return http.HandlerFunc(fn)
}

The comment in that snippet is the part that is yours to write: gologin gets you to the token and the user, and granting a session is left to your code.

DebugOnlyCookieConfig is the default in the example and wrong in production

The StateHandler is the CSRF protection for the OAuth2 flow. The README describes its behaviour: it checks for an OAuth2 state parameter cookie, generates a non-guessable state as a short-lived cookie if one is missing, and passes the state value in the context. The CookieConfig lets you change the cookie name or the expiration, which defaults to 60 seconds.

The example uses `gologin.DebugOnlyCookieConfig`, and the README immediately says that in production you should use a config like `gologin.DefaultCookieConfig`, which sets Secure to true so the cookie is only sent over HTTPS. The name is a fair warning.

If you want to persist the state parameter differently, the README says to chain your own http.Handler in front. That is the escape hatch, and it is the reason the StateHandler is a handler rather than a fixed step: replacing the cookie storage with something server-side is a matter of wrapping, not rewriting.

Read this before the first deployment. A CSRF state cookie that is not marked Secure is exactly the kind of default that survives a code review because it looks like configuration.

OAuth1 needs a request secret in the context, and that is the difference

The Twitter example shows the OAuth1 path, and it is a different shape because the protocol has an extra round trip. The config carries a ConsumerKey, ConsumerSecret, CallbackURL and the authorize Endpoint:

go
config := &oauth1.Config{
    ConsumerKey:    "TwitterConsumerKey",
    ConsumerSecret: "TwitterConsumerSecret",
    CallbackURL:    "http://localhost:8080/callback",
    Endpoint:       twitterOAuth1.AuthorizeEndpoint,
}
mux := http.NewServeMux()
mux.Handle("/login", twitter.LoginHandler(config, nil))
mux.Handle("/callback", twitter.CallbackHandler(config, issueSession(), nil))

The twitter LoginHandler obtains a request token and secret, puts them in the context, and redirects the user to the authorize URL. The callback receives a token and a verifier, reads the request secret back out of the context, and exchanges them for an OAuth1 access token and secret. Note what is absent compared with the OAuth2 example: no StateHandler, because OAuth1's request secret serves the anti-forgery role instead.

That is why the README lists configurable OAuth1 request secret handling as a separate feature from the OAuth2 state parameter handling. The success handler retrieves the pair with `oauth1Login.AccessTokenFromContext(ctx)` and the user with `twitter.UserFromContext(ctx)`.

Native mobile logins use TokenHandler, not the callback pair

The three handler types cover three flows, and the README is explicit that mobile is the third. LoginHandler and CallbackHandler support web login flows, where the user is redirected to the provider and back. TokenHandler supports native mobile token login flows, where the app obtains a token and hands it to your service.

The repository layout shows the provider set concretely. There are subpackage directories for google, github, twitter, facebook, bitbucket and tumblr, alongside the generic oauth1 and oauth2 packages, plus internal/, testutils/, config.go, context.go and errors.go at the root. Anything beyond these six providers means writing to the oauth1 or oauth2 layer yourself.

That boundary is the important one. gologin is not a general identity provider; it is a set of provider adapters over two protocols, with google using google.golang.org/api, github using go-github, and twitter using go-twitter, all as separate dependencies.

Dependencies pull in an entire Google API client whether you use it or not

The module is github.com/dghubble/gologin/v2, requiring Go 1.26.0, so the import paths in your code carry the /v2 suffix. That detail matters if you are upgrading from a v1 import, because the path is what the toolchain uses to pick the major version.

The dependency list explains the weight. Direct requirements include golang.org/x/oauth2 v0.37.0, github.com/google/go-github/v89 v89.0.0, github.com/dghubble/go-twitter, github.com/dghubble/oauth1 v0.7.3, github.com/dghubble/sling v1.4.2, google.golang.org/api v0.304.0 and testify for tests. The google.golang.org/api requirement is what drags in the long indirect list: cloud.google.com/go/auth, the OpenTelemetry packages, google.golang.org/grpc and google.golang.org/protobuf all appear as indirect requirements.

That is the practical cost of a single module holding all six providers, and the reason to weigh this against writing the two handler calls directly against golang.org/x/oauth2 if your provider list is short and fixed.

Releases stopped in 2024 while the module kept moving

The version history needs reading carefully, because the two signals disagree. The last tagged release is v2.5.0, dated 2024-02-26, with v2.4.0 in January 2023 and v2.3.1 in December 2022. By that measure the project looks quiet.

The repository itself is not. The last push was on 2026-10-14, and go.mod includes dependency pseudo-versions dated in October 2026: cloud.google.com/go/auth carries 2026-10-01, while genproto/googleapis/rpc carries 2026-10-05. Those cannot come from a repository that stopped in February 2024.

The accurate description is that v2.5.0 is the newest tag while main continues to receive maintenance against current dependencies. If your dependency management requires the latest listed tag, it selects v2.5.0. If you follow main, note that the README's own import guidance and the tagged documentation may differ, and that there is no release after 2.5.0 whose changelog you can read in CHANGES.md.

The Makefile is minimal and says what the project expects of itself: `test` runs go test with coverage across every package except examples, `vet` runs go vet with -all, and `fmt` fails if go fmt would change anything.

Editorial conclusion

Adopt gologin when you serve Go's net/http and want a provider-specific user object without writing the OAuth dance per provider, and use `gologin.DefaultCookieConfig` rather than `gologin.DebugOnlyCookieConfig` the moment you are not on localhost. Do not adopt it for OpenID Connect, SAML or passkey flows, none of which appear in the package list, nor if you would rather have a single framework that owns your routes and sessions. Verify first which subpackage you need against your provider list, and confirm that the go-github and go-twitter major versions in go.mod still match what you pin.

Frequently asked questions

How do I add a GitHub OAuth2 login flow with gologin?

Build an oauth2.Config with the ClientID, ClientSecret, RedirectURL and Endpoint, then wrap github.LoginHandler and github.CallbackHandler in a github.StateHandler and register them on your mux. The authenticated token and user are read from the request context with oauth2Login.TokenFromContext and github.UserFromContext.

What is the difference between gologin's DebugOnlyCookieConfig and DefaultCookieConfig?

Both are OAuth2 state parameter cookie configs. DefaultCookieConfig sets Secure to true so the cookie is only sent over HTTPS, and the README says to use it in production, while DebugOnlyCookieConfig is what the example uses for local development.

Which OAuth providers does gologin support?

The repository has subpackages for google, github, twitter, facebook, bitbucket and tumblr, all built on the lower level oauth1 and oauth2 packages. Any provider not in that list means writing against the oauth1 or oauth2 layer directly.

How does gologin handle native mobile login?

Through the TokenHandler, which supports native mobile token login flows. LoginHandler and CallbackHandler are for web flows where the user is redirected to the provider and back to your callback URL.

Is gologin still being updated?

The repository is not archived, the last push was on 2026-10-14, and go.mod includes dependency pseudo-version dates in October 2026. The most recent tagged release is v2.5.0 from 2024-02-26, so the tags lag behind the default branch.

Official sources

  1. dghubble/gologin on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dghubble-gologin.svg)](https://hysenlabs.com/projects/dghubble-gologin)

Was this useful?