Skip to content

Commit

Permalink
Rework in order to work with Traefik
Browse files Browse the repository at this point in the history
  • Loading branch information
StiviiK committed Jun 3, 2020
1 parent 2ab3e1b commit 530ad44
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 147 deletions.
46 changes: 0 additions & 46 deletions .vscode/launch.json

This file was deleted.

7 changes: 3 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,11 @@ func main() {
}

// http handler
mux := http.ServeMux{}
mux.HandleFunc("/", httphandler.RootHandler(fw, options))
mux.HandleFunc(fmt.Sprintf("/%s", options.RedirectURL), httphandler.CallbackHandler(fw, options))
httpHandler := httphandler.Create(fw, options)
http.HandleFunc("/", httpHandler.Entrypoint())

logrus.Infof("Listening on %d", options.Port)
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", options.Port), &mux))
logrus.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", options.Port), nil))
}

func checkOptions(options *options.Options) error {
Expand Down
69 changes: 36 additions & 33 deletions pkg/forwardauth/auth.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package forwardauth

import (
"context"
"encoding/json"
"errors"
"net/http"
"strings"

"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/options"
"github.com/sirupsen/logrus"
Expand All @@ -16,17 +16,17 @@ type AuthenticatationResult struct {
IDTokenClaims *json.RawMessage
}

func (fw *ForwardAuth) HandleAuthentication(logger *logrus.Entry, r *http.Request, state string) (*AuthenticatationResult, error) {
func (fw *ForwardAuth) HandleAuthentication(ctx context.Context, logger *logrus.Entry, state string, code string) (*AuthenticatationResult, error) {
var result AuthenticatationResult
logger = logger.WithField("FunctionSource", "HandleAuthentication")

oauth2Token, err := fw.OAuth2Config.Exchange(r.Context(), r.URL.Query().Get("code"))
oauth2Token, err := fw.OAuth2Config.Exchange(ctx, code)
if err != nil {
logger.Error(err.Error())
return &result, err
}

result, err = fw.VerifyToken(r.Context(), oauth2Token)
result, err = fw.VerifyToken(ctx, oauth2Token)
if err != nil {
logger.Error(err.Error())
return &result, err
Expand All @@ -36,7 +36,7 @@ func (fw *ForwardAuth) HandleAuthentication(logger *logrus.Entry, r *http.Reques
return &result, nil
}

func (fw *ForwardAuth) IsAuthenticated(logger *logrus.Entry, w http.ResponseWriter, r *http.Request, options *options.Options) (*Claims, error) {
func (fw *ForwardAuth) IsAuthenticated(context context.Context, logger *logrus.Entry, w http.ResponseWriter, r *http.Request, options *options.Options) (*Claims, error) {
var claims Claims
logger = logger.WithField("FunctionSource", "IsAuthenticated")

Expand All @@ -48,7 +48,7 @@ func (fw *ForwardAuth) IsAuthenticated(logger *logrus.Entry, w http.ResponseWrit
}

// check if the token is valid
idToken, err := fw.OidcVefifier.Verify(r.Context(), cookie.Value)
idToken, err := fw.OidcVefifier.Verify(context, cookie.Value)

switch {
case err == nil: // Token is valid
Expand All @@ -62,33 +62,36 @@ func (fw *ForwardAuth) IsAuthenticated(logger *logrus.Entry, w http.ResponseWrit

return &claims, nil

case strings.Contains(err.Error(), "expired"): // Token is expired
logger.Info("Received expired token, trying to refesh it.")

refreshCookie, err := fw.GetRefreshAuthCookie(r)
if err != nil {
logger.Error(err.Error())
return &claims, err
}

result, err := fw.RefreshToken(r.Context(), refreshCookie.Value)
if err != nil {
logger.Error(err.Error())
return &claims, err
}

http.SetCookie(w, fw.MakeAuthCookie(r, options, result))
if len(result.RefreshToken) > 0 { // Do we have an refresh token?
http.SetCookie(w, fw.MakeRefreshAuthCookie(r, options, result))
}

err = json.Unmarshal(*result.IDTokenClaims, &claims)
if err != nil {
logger.Error(err.Error())
return &claims, err
}

return &claims, nil
// Todo: Updating the cookies does sadly not work here
/*
case strings.Contains(err.Error(), "expired"): // Token is expired
logger.Info("Received expired token, trying to refesh it.")
refreshCookie, err := fw.GetRefreshAuthCookie(r)
if err != nil {
logger.Error(err.Error())
return &claims, err
}
result, err := fw.RefreshToken(context, refreshCookie.Value)
if err != nil {
logger.Error(err.Error())
return &claims, err
}
http.SetCookie(w, fw.MakeAuthCookie(options, result))
if len(result.RefreshToken) > 0 { // Do we have an refresh token?
http.SetCookie(w, fw.MakeRefreshAuthCookie(options, result))
}
err = json.Unmarshal(*result.IDTokenClaims, &claims)
if err != nil {
logger.Error(err.Error())
return &claims, err
}
return &claims, nil
*/

case err != nil: // Other error
logger.Error(err.Error())
Expand Down
6 changes: 3 additions & 3 deletions pkg/forwardauth/cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func getBaseCookie(options *options.Options) *http.Cookie {
func (fw *ForwardAuth) MakeCSRFCookie(w http.ResponseWriter, r *http.Request, options *options.Options, state string) *http.Cookie {
cookie := getBaseCookie(options)
cookie.Name = "__auth_csrf"
cookie.Value = fmt.Sprintf("%s|%s", "//google.de", state)
cookie.Value = fmt.Sprintf("%s|%s", fw.GetReturnUri(r), state)
cookie.Expires = time.Now().Local().Add(time.Hour)

return cookie
Expand Down Expand Up @@ -60,7 +60,7 @@ func (fw *ForwardAuth) ClearCSRFCookie(options *options.Options) *http.Cookie {
return cookie
}

func (fw *ForwardAuth) MakeAuthCookie(r *http.Request, options *options.Options, authResult *AuthenticatationResult) *http.Cookie {
func (fw *ForwardAuth) MakeAuthCookie(options *options.Options, authResult *AuthenticatationResult) *http.Cookie {
cookie := getBaseCookie(options)
cookie.Name = "__auth"
cookie.Value = authResult.IDToken
Expand All @@ -81,7 +81,7 @@ func (fw *ForwardAuth) ClearAuthCookie(options *options.Options) *http.Cookie {
return cookie
}

func (fw *ForwardAuth) MakeRefreshAuthCookie(r *http.Request, options *options.Options, authResult *AuthenticatationResult) *http.Cookie {
func (fw *ForwardAuth) MakeRefreshAuthCookie(options *options.Options, authResult *AuthenticatationResult) *http.Cookie {
cookie := getBaseCookie(options)
cookie.Name = "__auth_refresh"
cookie.Value = authResult.RefreshToken
Expand Down
2 changes: 1 addition & 1 deletion pkg/forwardauth/forwardauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func Create(ctx context.Context, options *options.Options) (*ForwardAuth, error)
OAuth2Config: oauth2.Config{
ClientID: options.ClientID,
ClientSecret: options.ClientSecret,
RedirectURL: fmt.Sprintf("http://%s:%d/%s", options.AuthDomain, options.Port, options.RedirectURL),
RedirectURL: fmt.Sprintf("https://%s%s", options.AuthDomain, options.RedirectURL),

// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
Expand Down
71 changes: 34 additions & 37 deletions pkg/httphandler/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,44 @@ package httphandler

import (
"net/http"
"net/url"

"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/forwardauth"
"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/options"
"github.com/sirupsen/logrus"
)

// CallbackHandler returns a handler function which handles the callback from oidc provider
func CallbackHandler(fw *forwardauth.ForwardAuth, options *options.Options) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
logger := logrus.WithFields(logrus.Fields{
"SourceIP": r.Header.Get("X-Forwarded-For"),
"Path": r.URL.Path,
})

// check for the csrf cookie
state, _, err := fw.ValidateCSRFCookie(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

// verify the state
if r.URL.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}

// handle the authentication
authResult, err := fw.HandleAuthentication(logger, r, state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// clear the csrf cookie
http.SetCookie(w, fw.ClearCSRFCookie(options))

http.SetCookie(w, fw.MakeAuthCookie(r, options, authResult))
if len(authResult.RefreshToken) > 0 { // Do we have an refresh token?
http.SetCookie(w, fw.MakeRefreshAuthCookie(r, options, authResult))
}
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
func (root *HttpHandler) callbackHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
logger := logrus.WithFields(logrus.Fields{
"SourceIP": r.Header.Get("X-Forwarded-For"),
"Path": forwardedURI.Path,
})

// check for the csrf cookie
state, redirect, err := root.forwardAuth.ValidateCSRFCookie(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}

// verify the state
if forwardedURI.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}

// handle the authentication
authResult, err := root.forwardAuth.HandleAuthentication(r.Context(), logger, state, forwardedURI.Query().Get("code"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// clear the csrf cookie
http.SetCookie(w, root.forwardAuth.ClearCSRFCookie(root.options))

http.SetCookie(w, root.forwardAuth.MakeAuthCookie(root.options, authResult))
if len(authResult.RefreshToken) > 0 { // Do we have an refresh token?
http.SetCookie(w, root.forwardAuth.MakeRefreshAuthCookie(root.options, authResult))
}
http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
}
40 changes: 40 additions & 0 deletions pkg/httphandler/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package httphandler

import (
"net/http"
"net/url"

"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/forwardauth"
"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/options"
)

type HttpHandler struct {
forwardAuth *forwardauth.ForwardAuth
options *options.Options
}

func Create(fw *forwardauth.ForwardAuth, options *options.Options) *HttpHandler {
return &HttpHandler{
forwardAuth: fw,
options: options,
}
}

func (h *HttpHandler) Entrypoint() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
uri, err := url.Parse(r.Header.Get("X-Forwarded-Uri"))
switch {
case err != nil:
http.Error(w, err.Error(), http.StatusInternalServerError)
return

case uri.Path == h.options.RedirectURL:
h.callbackHandler(w, r, uri)
return

case uri.Path == "/":
h.rootHandler(w, r, uri)
return
}
}
}
43 changes: 20 additions & 23 deletions pkg/httphandler/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,34 @@ package httphandler

import (
"net/http"
"net/url"

"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/forwardauth"
"github.com/StiviiK/keycloak-traefik-forward-auth/pkg/options"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)

// RootHandler returns a handler function which handles all requests to the root
func RootHandler(fw *forwardauth.ForwardAuth, options *options.Options) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
logger := logrus.WithFields(logrus.Fields{
"SourceIP": r.Header.Get("X-Forwarded-For"),
"Path": r.URL.Path,
})
func (root *HttpHandler) rootHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
logger := logrus.WithFields(logrus.Fields{
"SourceIP": r.Header.Get("X-Forwarded-For"),
"Path": forwardedURI.Path,
})

claims, err := fw.IsAuthenticated(logger, w, r, options)
if err != nil {
logger = logger.WithField("FunctionSource", "RootHandler")
logger.Warn("IsAuthenticated failed, initating login flow.")
claims, err := root.forwardAuth.IsAuthenticated(r.Context(), logger, w, r, root.options)
if err != nil {
logger = logger.WithField("FunctionSource", "RootHandler")
logger.Warn("IsAuthenticated failed, initating login flow.")

http.SetCookie(w, fw.ClearAuthCookie(options))
http.SetCookie(w, fw.ClearRefreshAuthCookie(options))
http.SetCookie(w, root.forwardAuth.ClearAuthCookie(root.options))
http.SetCookie(w, root.forwardAuth.ClearRefreshAuthCookie(root.options))

state := uuid.New().String()
http.SetCookie(w, fw.MakeCSRFCookie(w, r, options, state))
http.Redirect(w, r, fw.OAuth2Config.AuthCodeURL(state), http.StatusFound)
return
}

w.Header().Set("X-Forwarded-User", claims.EMail)
w.WriteHeader(200)
w.Write([]byte(claims.Expiration.Time().Local().String()))
state := uuid.New().String()
http.SetCookie(w, root.forwardAuth.MakeCSRFCookie(w, r, root.options, state))
http.Redirect(w, r, root.forwardAuth.OAuth2Config.AuthCodeURL(state), http.StatusTemporaryRedirect)
return
}

w.Header().Set("X-Forwarded-User", claims.EMail)
w.WriteHeader(200)
w.Write([]byte(claims.Expiration.Time().Local().String()))
}

0 comments on commit 530ad44

Please sign in to comment.