diff --git a/server/auth/github/github.go b/server/auth/github/github.go
index d5464780df5301c563e9688278064615473055d1..38009e107b9a8fda55459b40bb52233a3bab917d 100644
--- a/server/auth/github/github.go
+++ b/server/auth/github/github.go
@@ -97,10 +97,8 @@ func (c *Config) Revoke(token *oauth2.Token) error {
 }
 
 // StartSession retrieves an authentication endpoint from Github.
-func (c *Config) StartSession(state string) *auth.Session {
-	return &auth.Session{
-		AuthURL: c.config.AuthCodeURL(state),
-	}
+func (c *Config) StartSession(state string) string {
+	return c.config.AuthCodeURL(state)
 }
 
 // Exchange authorizes the session and returns an access token.
diff --git a/server/auth/github/github_test.go b/server/auth/github/github_test.go
index 8c51f4f8f13021342f6a93eb87e56ea3c5ca2b16..9e94f9a8d6694c31db0e823e88d6e72221913d9d 100644
--- a/server/auth/github/github_test.go
+++ b/server/auth/github/github_test.go
@@ -62,9 +62,9 @@ func TestStartSession(t *testing.T) {
 
 	p, _ := newGithub()
 	s := p.StartSession("test_state")
-	a.Contains(s.AuthURL, "github.com/login/oauth/authorize")
-	a.Contains(s.AuthURL, "state=test_state")
-	a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID))
+	a.Contains(s, "github.com/login/oauth/authorize")
+	a.Contains(s, "state=test_state")
+	a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
 }
 
 func newGithub() (*Config, error) {
diff --git a/server/auth/gitlab/gitlab.go b/server/auth/gitlab/gitlab.go
index 2cf2a5c29b353a7ab114639fc02f31a122de5c2a..5e1f95f7d3c497aee921ce59d92fc9d0afa80405 100644
--- a/server/auth/gitlab/gitlab.go
+++ b/server/auth/gitlab/gitlab.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"strconv"
 
-	"github.com/nsheridan/cashier/server/auth"
 	"github.com/nsheridan/cashier/server/config"
 	"github.com/nsheridan/cashier/server/metrics"
 
@@ -114,10 +113,8 @@ func (c *Config) Revoke(token *oauth2.Token) error {
 }
 
 // StartSession retrieves an authentication endpoint from Gitlab.
-func (c *Config) StartSession(state string) *auth.Session {
-	return &auth.Session{
-		AuthURL: c.config.AuthCodeURL(state),
-	}
+func (c *Config) StartSession(state string) string {
+	return c.config.AuthCodeURL(state)
 }
 
 // Exchange authorizes the session and returns an access token.
diff --git a/server/auth/gitlab/gitlab_test.go b/server/auth/gitlab/gitlab_test.go
index 39c2701245372b0775778a20e9afce28ab36706b..93b348b1cc96cb4df776edb599a7a578c08b2545 100644
--- a/server/auth/gitlab/gitlab_test.go
+++ b/server/auth/gitlab/gitlab_test.go
@@ -56,9 +56,9 @@ func TestGoodAllUsers(t *testing.T) {
 
 	p, _ := newGitlab()
 	s := p.StartSession("test_state")
-	a.Contains(s.AuthURL, "exampleorg/oauth/authorize")
-	a.Contains(s.AuthURL, "state=test_state")
-	a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID))
+	a.Contains(s, "exampleorg/oauth/authorize")
+	a.Contains(s, "state=test_state")
+	a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
 
 	allusers = ""
 }
@@ -78,9 +78,9 @@ func TestStartSession(t *testing.T) {
 
 	p, _ := newGitlab()
 	s := p.StartSession("test_state")
-	a.Contains(s.AuthURL, "exampleorg/oauth/authorize")
-	a.Contains(s.AuthURL, "state=test_state")
-	a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID))
+	a.Contains(s, "exampleorg/oauth/authorize")
+	a.Contains(s, "state=test_state")
+	a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
 }
 
 func newGitlab() (auth.Provider, error) {
diff --git a/server/auth/google/google.go b/server/auth/google/google.go
index 9a151f62daddbb8707fffe980284a8f4eb23f76f..b707310061abacef27751a05010855d1be4acf81 100644
--- a/server/auth/google/google.go
+++ b/server/auth/google/google.go
@@ -103,10 +103,8 @@ func (c *Config) Revoke(token *oauth2.Token) error {
 }
 
 // StartSession retrieves an authentication endpoint from Google.
-func (c *Config) StartSession(state string) *auth.Session {
-	return &auth.Session{
-		AuthURL: c.config.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", c.domain)),
-	}
+func (c *Config) StartSession(state string) string {
+	return c.config.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", c.domain))
 }
 
 // Exchange authorizes the session and returns an access token.
diff --git a/server/auth/google/google_test.go b/server/auth/google/google_test.go
index b3d26334a9224b79612ed3cc915e26ece47eded7..92e4ca0a50008971af943385933f8163a17df40c 100644
--- a/server/auth/google/google_test.go
+++ b/server/auth/google/google_test.go
@@ -57,10 +57,10 @@ func TestStartSession(t *testing.T) {
 	p, err := newGoogle()
 	a.NoError(err)
 	s := p.StartSession("test_state")
-	a.Contains(s.AuthURL, "accounts.google.com/o/oauth2/auth")
-	a.Contains(s.AuthURL, "state=test_state")
-	a.Contains(s.AuthURL, fmt.Sprintf("hd=%s", domain))
-	a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID))
+	a.Contains(s, "accounts.google.com/o/oauth2/auth")
+	a.Contains(s, "state=test_state")
+	a.Contains(s, fmt.Sprintf("hd=%s", domain))
+	a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
 }
 
 func newGoogle() (*Config, error) {
diff --git a/server/auth/microsoft/microsoft.go b/server/auth/microsoft/microsoft.go
index 49d9b822b66a26e9a140a16b8ed91254568718eb..8463ccfef6e0cb9d40016adb2b8350ea20ebb4b6 100644
--- a/server/auth/microsoft/microsoft.go
+++ b/server/auth/microsoft/microsoft.go
@@ -175,12 +175,10 @@ func (c *Config) Revoke(token *oauth2.Token) error {
 }
 
 // StartSession retrieves an authentication endpoint from Microsoft.
-func (c *Config) StartSession(state string) *auth.Session {
-	return &auth.Session{
-		AuthURL: c.config.AuthCodeURL(state,
-			oauth2.SetAuthURLParam("hd", c.tenant),
-			oauth2.SetAuthURLParam("prompt", "login")),
-	}
+func (c *Config) StartSession(state string) string {
+	return c.config.AuthCodeURL(state,
+		oauth2.SetAuthURLParam("hd", c.tenant),
+		oauth2.SetAuthURLParam("prompt", "login"))
 }
 
 // Exchange authorizes the session and returns an access token.
diff --git a/server/auth/microsoft/microsoft_test.go b/server/auth/microsoft/microsoft_test.go
index c2c2c1722d9c011beb789e1ba003249bd6fc1fcb..e362ef926b4f2d51b9215c392ed3873d5f620a40 100644
--- a/server/auth/microsoft/microsoft_test.go
+++ b/server/auth/microsoft/microsoft_test.go
@@ -57,7 +57,7 @@ func TestStartSession(t *testing.T) {
 	p, err := newMicrosoft()
 	a.NoError(err)
 	s := p.StartSession("test_state")
-	a.Contains(s.AuthURL, fmt.Sprintf("login.microsoftonline.com/%s/oauth2/v2.0/authorize", tenant))
+	a.Contains(s, fmt.Sprintf("login.microsoftonline.com/%s/oauth2/v2.0/authorize", tenant))
 }
 
 func newMicrosoft() (*Config, error) {
diff --git a/server/auth/provider.go b/server/auth/provider.go
index 06dc1c9a0b2111ea9f9bc4e66c1c038cb1013523..9d1c8bd3b9538469078e1c27d00ae1afc6374c27 100644
--- a/server/auth/provider.go
+++ b/server/auth/provider.go
@@ -5,26 +5,9 @@ import "golang.org/x/oauth2"
 // Provider is an abstraction of different auth methods.
 type Provider interface {
 	Name() string
-	StartSession(string) *Session
+	StartSession(string) string
 	Exchange(string) (*oauth2.Token, error)
 	Username(*oauth2.Token) string
 	Valid(*oauth2.Token) bool
 	Revoke(*oauth2.Token) error
 }
-
-// Session stores authentication state.
-type Session struct {
-	AuthURL string
-	Token   *oauth2.Token
-}
-
-// Authorize obtains data from the provider and retains an access token that
-// can be stored for later access.
-func (s *Session) Authorize(provider Provider, code string) error {
-	t, err := provider.Exchange(code)
-	if err != nil {
-		return err
-	}
-	s.Token = t
-	return nil
-}
diff --git a/server/auth/testprovider/testprovider.go b/server/auth/testprovider/testprovider.go
index e30b04aaa58e8cae689cb47b6b992ead3ebe02f4..f785081a328bfe9c591da7153c21700474c05667 100644
--- a/server/auth/testprovider/testprovider.go
+++ b/server/auth/testprovider/testprovider.go
@@ -38,10 +38,8 @@ func (c *Config) Revoke(token *oauth2.Token) error {
 }
 
 // StartSession retrieves an authentication endpoint.
-func (c *Config) StartSession(state string) *auth.Session {
-	return &auth.Session{
-		AuthURL: "https://www.example.com/auth",
-	}
+func (c *Config) StartSession(state string) string {
+	return "https://www.example.com/auth"
 }
 
 // Exchange authorizes the session and returns an access token.
diff --git a/server/handlers_test.go b/server/handlers_test.go
index 7f3145242a82753e39f41b27234bf098661f13ec..6dc2236cd442fd4f323aa5c2b2f32aa2961b9605 100644
--- a/server/handlers_test.go
+++ b/server/handlers_test.go
@@ -17,7 +17,6 @@ import (
 
 	"github.com/gorilla/sessions"
 	"github.com/nsheridan/cashier/lib"
-	"github.com/nsheridan/cashier/server/auth"
 	"github.com/nsheridan/cashier/server/auth/testprovider"
 	"github.com/nsheridan/cashier/server/config"
 	"github.com/nsheridan/cashier/server/signer"
@@ -41,7 +40,6 @@ func init() {
 	certstore, _ = store.New(map[string]string{"type": "mem"})
 	ctx = &appContext{
 		cookiestore: sessions.NewCookieStore([]byte("secret")),
-		authsession: &auth.Session{AuthURL: "https://www.example.com/auth"},
 	}
 }
 
diff --git a/server/web.go b/server/web.go
index 840ce1ba1c9113c5c338caa89e61fe85b2cce681..9114de1f010eedbed323884bdfbb3ff32fc2590b 100644
--- a/server/web.go
+++ b/server/web.go
@@ -28,7 +28,6 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gorilla/sessions"
 	"github.com/nsheridan/cashier/lib"
-	"github.com/nsheridan/cashier/server/auth"
 	"github.com/nsheridan/cashier/server/config"
 	"github.com/nsheridan/cashier/server/templates"
 )
@@ -36,7 +35,6 @@ import (
 // appContext contains local context - cookiestore, authsession etc.
 type appContext struct {
 	cookiestore   *sessions.CookieStore
-	authsession   *auth.Session
 	requireReason bool
 }
 
@@ -172,8 +170,7 @@ func signHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, er
 func loginHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, error) {
 	state := newState()
 	a.setAuthStateCookie(w, r, state)
-	a.authsession = authprovider.StartSession(state)
-	http.Redirect(w, r, a.authsession.AuthURL, http.StatusFound)
+	http.Redirect(w, r, authprovider.StartSession(state), http.StatusFound)
 	return http.StatusFound, nil
 }
 
@@ -183,10 +180,11 @@ func callbackHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int
 		return http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized))
 	}
 	code := r.FormValue("code")
-	if err := a.authsession.Authorize(authprovider, code); err != nil {
+	token, err := authprovider.Exchange(code)
+	if err != nil {
 		return http.StatusInternalServerError, err
 	}
-	a.setAuthTokenCookie(w, r, a.authsession.Token)
+	a.setAuthTokenCookie(w, r, token)
 	http.Redirect(w, r, a.getCurrentURL(r), http.StatusFound)
 	return http.StatusFound, nil
 }