Skip to content

Commit

Permalink
feat: allow overriding callback url after auth flow finishes
Browse files Browse the repository at this point in the history
- callback_urls is added in config that takes array of urls
- authorized_redirect_urls is added in config to whitelist endpoints
allowed to redirect user in browser after auth is finished in frontier
- cors_origin is now an array of strings

Signed-off-by: Kush <[email protected]>
  • Loading branch information
kushsharma committed Aug 13, 2023
1 parent 25ee189 commit 0fb77ca
Show file tree
Hide file tree
Showing 20 changed files with 4,032 additions and 3,813 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
VERSION := $(shell git describe --tags ${TAG})
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto ui
.DEFAULT_GOAL := build
PROTON_COMMIT := "fba5bc5edb16a65200afe8b731ca8ca4d7f3f054"
PROTON_COMMIT := "aa720c9d2762ff83b298188c9f306ac32d343180"

ui:
@echo " > generating ui build"
Expand All @@ -19,7 +19,9 @@ build:
CGO_ENABLED=0 go build -ldflags "-X ${NAME}/config.Version=${VERSION}" -o frontier .

generate: ## run all go generate in the code base (including generating mock files)
go generate ./...
@go generate ./...
@echo " > generating mock files"
@mockery

lint: ## Run linters
golangci-lint run
Expand Down
10 changes: 0 additions & 10 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,5 @@ func Load(serverConfigFileFromFlag string) (*Frontier, error) {
}
}

// post config load hooks for backward compatibility
conf = postLoad(conf)

return conf, nil
}

func postLoad(conf *Frontier) *Frontier {
if conf.App.Authentication.OIDCCallbackHost != "" {
conf.App.Authentication.CallbackHost = conf.App.Authentication.OIDCCallbackHost
}
return conf
}
15 changes: 12 additions & 3 deletions config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ app:
# disable_orgs_listing if set to true will disallow non-admin APIs to list all users
disable_users_listing: false
# cors_origin is origin value from where we want to allow cors
cors_origin: http://localhost:3000
cors_origin: ["http://localhost:3000"]
# configuration to allow authentication in shield
authentication:
# to use shield as session store
Expand All @@ -57,9 +57,18 @@ app:
iss: "http://localhost.shield"
# validity of the token
validity: "1h"
# public facing host used for oidc redirect uri and mail link redirection
# Public facing host used for oidc redirect uri and mail link redirection
# after user credentials are verified.
# If frontier is exposed behind a proxy, this should set as proxy endpoint
# e.g. http://localhost:7400/v1beta1/auth/callback
callback_host: http://localhost:8000/v1beta1/auth/callback
# Only the first host is used for callback by default, if multiple hosts are provided
# they can be used to override the callback host for specific strategies using query param
callback_urls: ["http://localhost:8000/v1beta1/auth/callback"]
# by default, after successful authentication(flow completes) no operation will be performed,
# to apply redirection in case of browsers, provide a list of urls one of which will be used
# after authentication where users will be redirected to.
# this is optional
authorized_redirect_urls: []
# oidc auth server configs
oidc_config:
google:
Expand Down
14 changes: 11 additions & 3 deletions core/authenticate/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,17 @@ func (f Flow) IsValid(currentTime time.Time) bool {
}

type RegistrationStartRequest struct {
Method string
ReturnTo string
Email string
Method string
// ReturnToURL is where flow should end to after successful verification
ReturnToURL string
Email string

// callback_url will be used by strategy as last step to finish authentication flow
// in OIDC this host will receive "state" and "code" query params, in case of magic links
// this will be the url where user is redirected after clicking on magic link.
// For most cases it could be host of frontier but in case of proxies, this will be proxy public endpoint.
// callback_url should be one of the allowed urls configured at instance level
CallbackUrl string
}

type RegistrationFinishRequest struct {
Expand Down
9 changes: 4 additions & 5 deletions core/authenticate/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package authenticate
import "time"

type Config struct {
// CallbackHost is external host used for redirect uri
CallbackHost string `yaml:"callback_host" mapstructure:"callback_host" default:"http://localhost:7400/v1beta1/auth/callback"`
// CallbackURLs is external host used for redirect uri
// host specified at 0th index will be used as default
CallbackURLs []string `yaml:"callback_urls" mapstructure:"callback_urls" default:"[http://localhost:7400/v1beta1/auth/callback]"`

// OIDCCallbackHost is external host used for redirect uri
// Deprecated: use CallbackHost instead
OIDCCallbackHost string `yaml:"oidc_callback_host" mapstructure:"oidc_callback_host" default:"http://localhost:7400/v1beta1/auth/oidc/callback"`
AuthorizedRedirectURLs []string `yaml:"authorized_redirect_urls" mapstructure:"authorized_redirect_urls" `

OIDCConfig map[string]OIDCConfig `yaml:"oidc_config" mapstructure:"oidc_config"`
Session SessionConfig `yaml:"session" mapstructure:"session"`
Expand Down
52 changes: 48 additions & 4 deletions core/authenticate/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"strings"
"time"

"github.com/raystack/frontier/pkg/metadata"

"github.com/raystack/frontier/core/audit"

"golang.org/x/exp/slices"
Expand Down Expand Up @@ -115,16 +117,48 @@ func (s Service) SupportedStrategies() []string {
return strategies
}

// SanitizeReturnToURL allows only redirect to white listed domains from config
// to avoid https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html
func (s Service) SanitizeReturnToURL(url string) string {
if len(url) == 0 {
return ""
}
if len(s.config.AuthorizedRedirectURLs) == 0 {
return ""
}
if slices.Contains[[]string](s.config.AuthorizedRedirectURLs, url) {
return url
}
return ""
}

// SanitizeCallbackURL allows only callback host to white listed domains from config
func (s Service) SanitizeCallbackURL(url string) string {
if len(s.config.CallbackURLs) == 0 {
return ""
}
if len(url) == 0 {
return s.config.CallbackURLs[0]
}
if slices.Contains[[]string](s.config.CallbackURLs, url) {
return url
}
return ""
}

func (s Service) StartFlow(ctx context.Context, request RegistrationStartRequest) (*RegistrationStartResponse, error) {
if !utils.Contains(s.SupportedStrategies(), request.Method) {
return nil, ErrUnsupportedMethod
}
flow := &Flow{
ID: uuid.New(),
Method: request.Method,
FinishURL: request.ReturnTo,
FinishURL: request.ReturnToURL,
CreatedAt: s.Now(),
ExpiresAt: s.Now().Add(defaultFlowExp),
Metadata: metadata.Metadata{
"callback_url": request.CallbackUrl,
},
}

if request.Method == MailOTPAuthMethod.String() {
Expand All @@ -147,8 +181,13 @@ func (s Service) StartFlow(ctx context.Context, request RegistrationStartRequest
State: flow.ID.String(),
}, nil
}

if len(request.CallbackUrl) == 0 {
return nil, fmt.Errorf("callback url not configured")
}

if request.Method == MailLinkAuthMethod.String() {
mailLinkStrat := strategy.NewMailLink(s.mailDialer, s.config.CallbackHost, s.config.MailLink.Subject, s.config.MailLink.Body)
mailLinkStrat := strategy.NewMailLink(s.mailDialer, request.CallbackUrl, s.config.MailLink.Subject, s.config.MailLink.Body)
nonce, err := mailLinkStrat.SendMail(flow.ID.String(), request.Email)
if err != nil {
return nil, err
Expand All @@ -172,7 +211,7 @@ func (s Service) StartFlow(ctx context.Context, request RegistrationStartRequest
idp, err := strategy.NewRelyingPartyOIDC(
oidcConfig.ClientID,
oidcConfig.ClientSecret,
s.config.CallbackHost).
request.CallbackUrl).
Init(ctx, oidcConfig.IssuerUrl)
if err != nil {
return nil, err
Expand Down Expand Up @@ -304,10 +343,15 @@ func (s Service) applyOIDC(ctx context.Context, request RegistrationFinishReques
return nil, ErrStrategyNotApplicable
}

if _, ok := flow.Metadata["callback_url"]; !ok {
return nil, fmt.Errorf("callback url not configured")
}
callbackURL := flow.Metadata["callback_url"].(string)

idp, err := strategy.NewRelyingPartyOIDC(
oidcConfig.ClientID,
oidcConfig.ClientSecret,
s.config.CallbackHost).
callbackURL).
Init(ctx, oidcConfig.IssuerUrl)
if err != nil {
return nil, err
Expand Down
10 changes: 5 additions & 5 deletions docs/docs/authn/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ Or if frontier server is running behind a proxy server, you need to specify the
then forward the request to Frontier server at `/v1beta1/auth/callback`.

This callback url is used by the third party provider to redirect the user back to the Frontier server after successful
authentication. Same config needs to be specified in the `callback_host` field of the `config.yaml` file.
`callback_host` is required to generate the login URL for the third party provider that initiates the authentication
authentication. Same config needs to be specified in the `callback_urls` field of the `config.yaml` file.
`callback_urls` is required to generate the login URL for the third party provider that initiates the authentication
flow.

```yaml
Expand All @@ -62,7 +62,7 @@ app:
grpc:
port: 8001
# cors_origin is origin value from where we want to allow cors
cors_origin: http://localhost:3000
cors_origin: ["http://localhost:3000"]
# configuration to allow authentication in frontier
authentication:
# to use frontier as session store
Expand All @@ -85,7 +85,7 @@ app:
validity: "1h"
# public facing host used for oidc redirect uri and mail link redirection
# e.g. http://localhost:7400/v1beta1/auth/callback
callback_host: http://localhost:8000/v1beta1/auth/callback
callback_urls: ["http://localhost:8000/v1beta1/auth/callback"]
# oidc auth server configs
oidc_config:
google:
Expand Down Expand Up @@ -131,7 +131,7 @@ app:
grpc:
port: 8001
# cors_origin is origin value from where we want to allow cors
cors_origin: http://localhost:3000
cors_origin: ["http://localhost:3000"]
# configuration to allow authentication in frontier
authentication:
# to use frontier as session store
Expand Down
9 changes: 8 additions & 1 deletion docs/docs/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ app:
# disable_orgs_listing if set to true will disallow non-admin APIs to list all users
disable_users_listing: false
# cors_origin is origin value from where we want to allow cors
cors_origin: http://localhost:3000
cors_origin: ["http://localhost:3000"]
# configuration to allow authentication in frontier
authentication:
# to use frontier as session store
Expand All @@ -79,6 +79,13 @@ app:
iss: "http://localhost.frontier"
# validity of the token
validity: "1h"
# Public facing host used for oidc redirect uri and mail link redirection
# after user credentials are verified.
# If frontier is exposed behind a proxy, this should set as proxy endpoint
# e.g. http://localhost:7400/v1beta1/auth/callback
# Only the first host is used for callback by default, if multiple hosts are provided
# they can be used to override the callback host for specific strategies using query param
callback_urls: ["http://localhost:7400/v1beta1/auth/callback"]
mail_otp:
subject: "Frontier - Login Link"
# body is a go template with `Otp` as a variable
Expand Down
35 changes: 22 additions & 13 deletions docs/docs/reference/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ app:
# disable_orgs_listing if set to true will disallow non-admin APIs to list all users
disable_users_listing: false
# cors_origin is origin value from where we want to allow cors
cors_origin: http://localhost:3000
cors_origin: ["http://localhost:3000"]
# configuration to allow authentication in frontier
authentication:
# to use frontier as session store
Expand All @@ -63,9 +63,18 @@ app:
iss: "http://localhost.frontier"
# validity of the token
validity: "1h"
# public facing host used for oidc redirect uri and mail link redirection
# Public facing host used for oidc redirect uri and mail link redirection
# after user credentials are verified.
# If frontier is exposed behind a proxy, this should set as proxy endpoint
# e.g. http://localhost:7400/v1beta1/auth/callback
callback_host: http://localhost:8000/v1beta1/auth/callback
# Only the first host is used for callback by default, if multiple hosts are provided
# they can be used to override the callback host for specific strategies using query param
callback_urls: ["http://localhost:8000/v1beta1/auth/callback"]
# by default, after successful authentication(flow completes) no operation will be performed,
# to apply redirection in case of browsers, provide a list of urls one of which will be used
# after authentication where users will be redirected to.
# this is optional
authorized_redirect_urls: []
# oidc auth server configs
oidc_config:
google:
Expand Down Expand Up @@ -146,16 +155,16 @@ This page contains reference for all the application configurations for Frontier

Configuration to allow authentication in Frontier.

| **Field** | **Description** | **Required** | **Example** |
| -------------------------------------------------- |-----------------------------------------------------| ------------ | --------------------------------------------- |
| **app.authentication.session.hash_secret_key** | Secret key for session hashing. | Yes | "hash-secret-should-be-32-chars--" |
| **app.authentication.session.block_secret_key** | Secret key for session encryption. | Yes | "block-secret-should-be-32-chars-" |
| **app.authentication.token.rsa_path** | Path to the RSA key file for token authentication. | Yes | "./temp/rsa" |
| **app.authentication.token.iss** | Issuer URL for token authentication. | Yes | "http://localhost.frontier" |
| **app.authentication.callback_host** | External host used for OIDC/Mail link redirect URI. | Yes | "http://localhost:8000/v1beta1/auth/callback" |
| **app.authentication.oidc_config.google.client_id** | Google client ID for OIDC authentication. | No | "xxxxx.apps.googleusercontent.com" |
| **app.authentication.oidc_config.google.client_secret** | Google client secret for OIDC authentication. | No | "xxxxx" |
| **app.authentication.oidc_config.google.issuer_url** | Google issuer URL for OIDC authentication. | No | "https://accounts.google.com" |
| **Field** | **Description** | **Required** | **Example** |
| -------------------------------------------------- |-----------------------------------------------------| ------------ |---------------------------------------------------|
| **app.authentication.session.hash_secret_key** | Secret key for session hashing. | Yes | "hash-secret-should-be-32-chars--" |
| **app.authentication.session.block_secret_key** | Secret key for session encryption. | Yes | "block-secret-should-be-32-chars-" |
| **app.authentication.token.rsa_path** | Path to the RSA key file for token authentication. | Yes | "./temp/rsa" |
| **app.authentication.token.iss** | Issuer URL for token authentication. | Yes | "http://localhost.frontier" |
| **app.authentication.callback_urls** | External host used for OIDC/Mail link redirect URI. | Yes | "['http://localhost:8000/v1beta1/auth/callback']" |
| **app.authentication.oidc_config.google.client_id** | Google client ID for OIDC authentication. | No | "xxxxx.apps.googleusercontent.com" |
| **app.authentication.oidc_config.google.client_secret** | Google client secret for OIDC authentication. | No | "xxxxx" |
| **app.authentication.oidc_config.google.issuer_url** | Google issuer URL for OIDC authentication. | No | "https://accounts.google.com" |

### Admin Configurations

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/tour/setup-idp-oidc.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Follow the steps below to configure OIDC authentication via an external IDP:
- Add the following OIDC-related configurations under **`app.authentication`** section:

```yaml
callback_host: http://localhost:8000/v1beta1/auth/callback
callback_urls: ["http://localhost:8000/v1beta1/auth/callback"]
oidc_config:
google:
client_id: "xxxxx.apps.googleusercontent.com"
Expand All @@ -39,7 +39,7 @@ Follow the steps below to configure OIDC authentication via an external IDP:
- Replace **xxxxx.apps.googleusercontent.com** with your Google Client ID.
- Replace **xxxxx** with your Google Client Secret.
- Ensure that **callback_host** matches the **callback URL** you determined in step 1.
- Ensure that **callback_urls** matches the **callback URL** you determined in step 1.
- Update **issuer_url** if you're using a different IDP.
:::tip Tip
Expand Down
Loading

0 comments on commit 0fb77ca

Please sign in to comment.