Skip to content

Commit

Permalink
backend: Add OIDC Login Token auth endpoint
Browse files Browse the repository at this point in the history
This patch adds Login Token endpoint which
authenticates the user based on credentials
against the OIDC provider without the need
for browser flow. This endpoint can be used
to authenticate programmatically.

Signed-off-by: Santhosh Nagaraj S <santhosh@kinvolk.io>
  • Loading branch information
yolossn authored and illume committed Nov 17, 2021
1 parent aff0a2d commit 799c396
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 63 deletions.
28 changes: 27 additions & 1 deletion backend/api/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,25 @@ paths:
/login:
get:
operationId: login
description: login
description: login browser flow
responses:
"302":
description: login response
/login/token:
post:
operationId: loginToken
description: login token flow
requestBody:
description: payload for login token
required: true
content:
application/x-www-form-urlencoded:
schema:
$ref: "#/components/schemas/loginInfo"
responses:
"200":
description: login token response

/login/cb:
get:
operationId: loginCb
Expand Down Expand Up @@ -1064,6 +1079,17 @@ paths:
components:
schemas:
## request Body
loginInfo:
type: object
required:
- username
- password
properties:
username:
type: string
password:
type: string

appConfig:
type: object
required:
Expand Down
2 changes: 2 additions & 0 deletions backend/pkg/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type Authenticator interface {

LoginCb(ctx echo.Context) error

LoginToken(ctx echo.Context) error

ValidateToken(ctx echo.Context) error

LoginWebhook(ctx echo.Context) error
Expand Down
4 changes: 4 additions & 0 deletions backend/pkg/auth/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func (noa *noopAuth) LoginCb(ctx echo.Context) error {
return ctx.NoContent(http.StatusNotImplemented)
}

func (noa *noopAuth) LoginToken(ctx echo.Context) error {
return ctx.NoContent(http.StatusNotImplemented)
}

func (noa *noopAuth) ValidateToken(ctx echo.Context) error {
return ctx.NoContent(http.StatusNotImplemented)
}
Expand Down
62 changes: 61 additions & 1 deletion backend/pkg/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type oidcAuth struct {
oauthConfig *oauth2.Config
defaultTeamID string
clientID string // OIDC Client ID
clientSecret string // OIDC Client Secret
issuerURL string // OIDC Issuer URL
callbackURL string // OIDC Callback URL, should be configured in OIDC provider. Default value is: http://localhost:8000/login/cb
validRedirectURLs []string // List of valid redirect URLs that the browser can redirect to after successful login
Expand All @@ -65,6 +66,19 @@ type oidcAuth struct {
sessionStore *sessions.Store
}

// OIDCTokenProviderResp is used to bind response from OIDC provider password grant_type
type OIDCTokenProviderResp struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshExpiresIn int `json:"refresh_expires_in"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
IDToken string `json:"id_token"`
NotBeforePolicy int `json:"not-before-policy"`
SessionState string `json:"session_state"`
Scope string `json:"scope"`
}

func NewOIDCAuthenticator(config *OIDCAuthConfig) Authenticator {
ctx := context.Background()

Expand Down Expand Up @@ -99,6 +113,7 @@ func NewOIDCAuthenticator(config *OIDCAuthConfig) Authenticator {
oauthConfig: oauthConfig,
defaultTeamID: config.DefaultTeamID,
clientID: config.ClientID,
clientSecret: config.ClientSecret,
issuerURL: config.IssuerURL,
callbackURL: config.CallbackURL,
validRedirectURLs: config.ValidRedirectURLs,
Expand Down Expand Up @@ -223,7 +238,6 @@ func (oa *oidcAuth) LoginCb(c echo.Context) error {
q := redirectURL.Query()
q.Set("id_token", idToken)
redirectURL.RawQuery = q.Encode()
fmt.Println("Redirecting to", redirectURL.String())
redirectTo(c, redirectURL.String())
return nil
}
Expand Down Expand Up @@ -272,6 +286,52 @@ func (oa *oidcAuth) Login(c echo.Context) error {
return nil
}

func (oa *oidcAuth) LoginToken(c echo.Context) error {
requestID := c.Response().Writer.Header().Get("X-Request-ID")

resp, err := http.PostForm(oa.provider.Endpoint().TokenURL, url.Values{
"client_id": {oa.clientID},
"client_secret": {oa.clientSecret},
"grant_type": {"password"},
"scope": {strings.Join(oa.scopes, " ")},
"username": {c.Request().FormValue("username")},
"password": {c.Request().FormValue("password")},
})
if err != nil {
logger.Err(err).Str("request_id", requestID).Msg("OIDC provider password grant_type error")
return c.NoContent(http.StatusInternalServerError)
}

if resp.StatusCode != http.StatusOK {
logger.Error().Str("request_id", requestID).Msgf("Invalid response code %d from OIDC provider for password grant_type", resp.StatusCode)
return c.NoContent(http.StatusInternalServerError)
}
var oidcTokenResp OIDCTokenProviderResp

respDecoder := json.NewDecoder(resp.Body)
err = respDecoder.Decode(&oidcTokenResp)
if err != nil {
logger.Err(err).Str("request_id", requestID).Msg("Can't parse response from OIDC provider for password grant_type")
return c.NoContent(http.StatusInternalServerError)
}

oidcToken, err := oa.verifier.Verify(c.Request().Context(), oidcTokenResp.IDToken)
if err != nil {
logger.Error().Str("request_id", requestID).AnErr("error", err).Msg("Can't verify the token returned for password grant_type")
return c.NoContent(http.StatusInternalServerError)
}

// Store refresh_token in session
session := echosessions.GetSession(c)
session.Set("refresh_token", oidcTokenResp.RefreshToken)
session.Set("username", oidcToken.Subject)
sessionSave(c, session, "login_token")

return c.JSON(http.StatusOK, map[string]string{
"token": oidcTokenResp.AccessToken,
})
}

// tokenFromRequest extracts token from request header. If Authorization header is not present returns id_token query param .
func tokenFromRequest(c echo.Context) string {
token := c.Request().Header.Get("Authorization")
Expand Down
93 changes: 93 additions & 0 deletions backend/pkg/codegen/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions backend/pkg/codegen/server.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 799c396

Please sign in to comment.