-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/implement backend authentication #68
Changes from 2 commits
7618acd
e37a5e8
8431734
493fd96
ada7e13
e0b956f
946605b
c5100ed
87d36b7
9adb4ab
7f087b3
f31b28e
e490f66
2afbff7
a56bd7f
59bd685
bd45275
cdac4e5
4ec3b0f
c396393
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package authenticator | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"os" | ||
|
||
"github.com/coreos/go-oidc/v3/oidc" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
// Authenticator is used to authenticate our users. | ||
type Authenticator struct { | ||
*oidc.Provider | ||
oauth2.Config | ||
} | ||
|
||
func New() (*Authenticator, error) { | ||
provider, err := oidc.NewProvider( | ||
context.Background(), | ||
"https://"+os.Getenv("AUTH0_DOMAIN")+"/", | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
conf := oauth2.Config{ | ||
ClientID: os.Getenv("AUTH0_CLIENT_ID"), | ||
ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"), | ||
RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"), | ||
Endpoint: provider.Endpoint(), | ||
Scopes: []string{oidc.ScopeOpenID, "profile"}, | ||
} | ||
|
||
return &Authenticator{ | ||
Provider: provider, | ||
Config: conf, | ||
}, nil | ||
} | ||
|
||
func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) { | ||
rawIDToken, ok := token.Extra("id_token").(string) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I just read up more on the different token types for JWT. There is an ID token and Access token type. ID tokens should be used for authentication (i.e. a user is a valid user) and access tokens should be used for authorisation (i.e. a user is valid and is authorised to perform a certain action).
So you'll need to consider which APIs require an ID token, and which ones require an Access token and use the correct middleware accordingly. In our scenario, I believe we should be using Access tokens instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The quick start guide I was using is post login when I created the application. The guide only showed how to integrate authentication and not authorisation, hence the discrepancy here I think. Ill just refer to the guide you mentioned. |
||
if !ok { | ||
return nil, errors.New("no id_token field in oauth2 token") | ||
} | ||
|
||
oidcConfig := &oidc.Config{ | ||
ClientID: a.ClientID, | ||
} | ||
|
||
return a.Verifier(oidcConfig).Verify(ctx, rawIDToken) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package middleware | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"net/http" | ||
"os" | ||
|
||
"github.com/gorilla/sessions" | ||
|
||
"github.com/joshtyf/flowforge/src/api" | ||
"github.com/joshtyf/flowforge/src/authenticator" | ||
"github.com/joshtyf/flowforge/src/logger" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) | ||
|
||
func IsAuthenticated(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Add("Content-Type", "application/json") | ||
accessToken := r.Header.Get("Authorization") | ||
session, _ := store.Get(r, accessToken) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typically, the value in this header will be So you will probably need to do some string parsing to get the |
||
|
||
var profile map[string]interface{} | ||
profile, ok := session.Values["profile"].(map[string]interface{}) | ||
|
||
if ok { | ||
logger.Info("[Authorization] Previously authorised", nil) | ||
next.ServeHTTP(w, r) | ||
} | ||
|
||
authToken := &oauth2.Token{AccessToken: accessToken} | ||
if accessToken == "" { | ||
authError := &api.HandlerError{Error: errors.New("authorization token missing"), Code: http.StatusUnauthorized} | ||
w.WriteHeader(authError.Code) | ||
json.NewEncoder(w).Encode(authError) | ||
return | ||
} | ||
|
||
auth, err := authenticator.New() | ||
if err != nil { | ||
logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) | ||
authError := &api.HandlerError{Error: api.ErrInternalServerError, Code: http.StatusInternalServerError} | ||
w.WriteHeader(authError.Code) | ||
json.NewEncoder(w).Encode(authError) | ||
return | ||
} | ||
|
||
idToken, err := auth.VerifyIDToken(r.Context(), authToken) | ||
if err != nil { | ||
logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) | ||
authError := &api.HandlerError{Error: errors.New("unable to verify token"), Code: http.StatusUnauthorized} | ||
w.WriteHeader(authError.Code) | ||
json.NewEncoder(w).Encode(authError) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. encoding the error like this will not work. see my PR #65 description (or see this stack overflow answer https://stackoverflow.com/questions/44989924/golang-error-types-are-empty-when-encoded-to-json) |
||
return | ||
} | ||
|
||
err = idToken.Claims(&profile) | ||
if err != nil { | ||
logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) | ||
authError := &api.HandlerError{Error: errors.New("unable to retrieve profile"), Code: http.StatusInternalServerError} | ||
w.WriteHeader(authError.Code) | ||
json.NewEncoder(w).Encode(authError) | ||
return | ||
} | ||
|
||
session.Values["access_token"] = authToken.AccessToken | ||
session.Values["profile"] = profile | ||
err = session.Save(r, w) | ||
if err != nil { | ||
logger.Error("[Authorization] Unable save session", map[string]interface{}{"err": err}) | ||
authError := &api.HandlerError{Error: errors.New("unable to save session"), Code: http.StatusInternalServerError} | ||
w.WriteHeader(authError.Code) | ||
json.NewEncoder(w).Encode(authError) | ||
return | ||
} | ||
|
||
// Call the next middleware function or final handler | ||
next.ServeHTTP(w, r) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we be setting the scope here? I'm not too familiar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was based off their quick start guide so we just stick with this first until changes are required bah since it doesn't seem to create any immediate issues in my mind