-
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 10 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,51 @@ | ||
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"), | ||
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 |
---|---|---|
|
@@ -2,10 +2,13 @@ package server | |
|
||
import ( | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/joshtyf/flowforge/src/authenticator" | ||
"github.com/joshtyf/flowforge/src/database/models" | ||
"github.com/joshtyf/flowforge/src/logger" | ||
"github.com/joshtyf/flowforge/src/validation" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
func validateCreatePipelineRequest(next http.Handler) http.Handler { | ||
|
@@ -27,3 +30,43 @@ func validateCreatePipelineRequest(next http.Handler) http.Handler { | |
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
|
||
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 := strings.TrimSpace(strings.Replace(r.Header.Get("Authorization"), "Bearer", "", 1)) | ||
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. I'm not too sure about the security implications about this method of doing things. I think it's better to reject if the header doesn't contain the prefix |
||
authToken := &oauth2.Token{AccessToken: accessToken} | ||
|
||
if accessToken == "" { | ||
logger.Error("[Authorization] Bearer token not found", nil) | ||
encode(w, r, http.StatusInternalServerError, newHandlerError(ErrBearerTokenNotFound, http.StatusUnauthorized)) | ||
return | ||
} | ||
|
||
auth, err := authenticator.New() | ||
if err != nil { | ||
logger.Error("[Authorization] Failed to initialize authenticator", map[string]interface{}{"err": err}) | ||
encode(w, r, http.StatusInternalServerError, newHandlerError(ErrInternalServerError, http.StatusInternalServerError)) | ||
return | ||
} | ||
|
||
idToken, err := auth.VerifyIDToken(r.Context(), authToken) | ||
if err != nil { | ||
logger.Error("[Authorization] Unable to verify token", map[string]interface{}{"err": err}) | ||
encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToVerifyBearerToken, http.StatusUnauthorized)) | ||
return | ||
} | ||
|
||
// TODO: Review to see if this fits flow once frontend side of the flow is finalised | ||
var profile map[string]interface{} | ||
err = idToken.Claims(&profile) | ||
if err != nil { | ||
logger.Error("[Authorization] Unable retrieve profile", map[string]interface{}{"err": err}) | ||
encode(w, r, http.StatusInternalServerError, newHandlerError(ErrUnableToRetrieveProfile, http.StatusInternalServerError)) | ||
return | ||
} | ||
|
||
// Call the next middleware function or final handler | ||
next.ServeHTTP(w, r) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ func addRoutes(r *mux.Router) { | |
func New() http.Handler { | ||
router := mux.NewRouter() | ||
addRoutes(router) | ||
router.Use(IsAuthenticated) | ||
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. We have 2 options:
Method 1 reduces the amount of code to write but it assumes that every handler requires authentication. It might just be better to opt for method 2, even though we have to repeat ourselves every time. Thoughts? reference: https://auth0.com/blog/id-token-access-token-what-is-the-difference/ 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. Thing is I can't think of a situation where authentication is not required. The only possibility would be a server side landing page, but since our backend is purely just API, there should not be any paths where authorisation middleware is not required. In that case, since the only differentiation is between authorisation for different roles, I think we just do two bah |
||
handlers.CORS( | ||
handlers.AllowedOrigins([]string{"http://localhost:3000"}), | ||
handlers.AllowedHeaders([]string{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ services: | |
- be | ||
build: | ||
context: backend | ||
env_file: .env | ||
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. you shouldn't need this field as 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. It doesn't source from the .env for me though. I needed to add it in to see the env variables 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. where are you running your |
||
environment: | ||
- MONGO_URI=${MONGO_URI} | ||
|
||
|
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