Skip to content

Commit

Permalink
Merge pull request #338 from canonical/IAM-892-cookie-auth-middleware
Browse files Browse the repository at this point in the history
IAM 892 - login handler and authentication middleware implementation according to spec ID036
  • Loading branch information
BarcoMasile authored Jul 1, 2024
2 parents 9cfdd69 + dee33e6 commit 61d0482
Show file tree
Hide file tree
Showing 12 changed files with 1,012 additions and 142 deletions.
5 changes: 4 additions & 1 deletion OAUTH2.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ OAUTH2_CLIENT_SECRET=client secret
OAUTH2_REDIRECT_URI=http://localhost:${PORT}/api/v0/auth/callback
OAUTH2_CODEGRANT_SCOPES=openid,offline_access (defaults to these two, technically two more would be needed: "profile,email")
OAUTH2_AUTH_COOKIES_ENCRYPTION_KEY="WrfOcYmVBwyduEbKYTUhO4X7XVaOQ1wF" (required, this needs to be exactly 32 bytes in lenght secret key)
OAUTH2_USER_SESSION_TTL_SECONDS=expiration in seconds for the refresh token cookie, should match the hydra refresh token ttl, (defaults to 21600)
ACCESS_TOKEN_VERIFICATION_STRATEGY="jwks|userinfo", (defaults to jwks)
```

Expand Down Expand Up @@ -79,7 +80,9 @@ The Hydra client for Admin UI needs to be set with the proper:
- response types: `"token,code,id_token"`
- grant types: `"authorization_code,refresh_token"`

If you use the Login UI [docker compose](https://github.com/canonical/identity-platform-login-ui/blob/main/docker-compose.yml) to deploy the solution with Github integration, you can first create an OAuth2
If you use the Login
UI [docker compose](https://github.com/canonical/identity-platform-login-ui/blob/main/docker-compose.yml) to deploy the
solution with Github integration, you can first create an OAuth2
client.
Then update it with the correct values, as in the following script:

Expand Down
1 change: 1 addition & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func serve() {
specs.OAuth2RedirectURI,
specs.AccessTokenVerificationStrategy,
specs.OAuth2AuthCookiesTTLSeconds,
specs.OAuth2UserSessionTTLSeconds,
specs.OAuth2AuthCookiesEncryptionKey,
specs.OAuth2CodeGrantScopes,
)
Expand Down
20 changes: 11 additions & 9 deletions internal/config/specs.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,17 @@ type EnvSpec struct {
HydraAdminURL string `envconfig:"hydra_admin_url" required:"true"`
OathkeeperPublicURL string `envconfig:"oathkeeper_public_url" required:"true"`

AuthenticationEnabled bool `envconfig:"authentication_enabled" default:"false" validate:"required"`
OIDCIssuer string `envconfig:"oidc_issuer" validate:"required"`
OAuth2ClientId string `envconfig:"oauth2_client_id" validate:"required"`
OAuth2ClientSecret string `envconfig:"oauth2_client_secret" validate:"required"`
OAuth2RedirectURI string `envconfig:"oauth2_redirect_uri" validate:"required"`
OAuth2CodeGrantScopes []string `envconfig:"oauth2_codegrant_scopes" default:"openid,offline_access" validate:"required"`
OAuth2AuthCookiesTTLSeconds int `envconfig:"oauth2_auth_cookies_ttl_seconds" default:"300" validate:"required"`
OAuth2AuthCookiesEncryptionKey string `envconfig:"oauth2_auth_cookies_encryption_key" required:"true" validate:"required,min=32,max=32"`
AccessTokenVerificationStrategy string `envconfig:"access_token_verification_strategy" default:"jwks" validate:"oneof=jwks userinfo"`
AuthenticationEnabled bool `envconfig:"authentication_enabled" default:"false" validate:"required"`
OIDCIssuer string `envconfig:"oidc_issuer" validate:"required"`
OAuth2ClientId string `envconfig:"oauth2_client_id" validate:"required"`
OAuth2ClientSecret string `envconfig:"oauth2_client_secret" validate:"required"`
OAuth2RedirectURI string `envconfig:"oauth2_redirect_uri" validate:"required"`
OAuth2CodeGrantScopes []string `envconfig:"oauth2_codegrant_scopes" default:"openid,offline_access" validate:"required"`
OAuth2AuthCookiesTTLSeconds int `envconfig:"oauth2_auth_cookies_ttl_seconds" default:"300" validate:"required"`
OAuth2UserSessionTTLSeconds int `envconfig:"oauth2_user_session_ttl_seconds" default:"21600" validate:"required"`

OAuth2AuthCookiesEncryptionKey string `envconfig:"oauth2_auth_cookies_encryption_key" required:"true" validate:"required,min=32,max=32"`
AccessTokenVerificationStrategy string `envconfig:"access_token_verification_strategy" default:"jwks" validate:"oneof=jwks userinfo"`

IDPConfigMapName string `envconfig:"idp_configmap_name" required:"true"`
IDPConfigMapNamespace string `envconfig:"idp_configmap_namespace" required:"true"`
Expand Down
73 changes: 65 additions & 8 deletions pkg/authentication/cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,61 @@ const (
authCookiePath = "/api/v0/auth/callback"
nonceCookieName = "nonce"
stateCookieName = "state"
itCookieName = "id-token"
atCookieName = "access-token"
rtCookieName = "refresh-token"
)

var (
epoch = time.Unix(0, 0).UTC()
)

type AuthCookieManager struct {
encrypt EncryptInterface
logger logging.LoggerInterface
authCookiesTTL time.Duration
userSessionCookieTTL time.Duration
encrypt EncryptInterface

logger logging.LoggerInterface
}

func (a *AuthCookieManager) SetIDTokenCookie(w http.ResponseWriter, rawIDToken string) {
a.setCookie(w, itCookieName, rawIDToken, "/", a.userSessionCookieTTL)
}

func (a *AuthCookieManager) GetIDTokenCookie(r *http.Request) string {
return a.getCookie(r, itCookieName)
}

func (a *AuthCookieManager) ClearIDTokenCookie(w http.ResponseWriter) {
a.clearCookie(w, itCookieName)
}

func (a *AuthCookieManager) SetAccessTokenCookie(w http.ResponseWriter, rawAccessToken string) {
a.setCookie(w, atCookieName, rawAccessToken, "/", a.userSessionCookieTTL)
}

func (a *AuthCookieManager) SetNonceCookie(w http.ResponseWriter, nonce string, ttl time.Duration) {
a.setCookie(w, nonceCookieName, nonce, authCookiePath, ttl)
func (a *AuthCookieManager) GetAccessTokenCookie(r *http.Request) string {
return a.getCookie(r, atCookieName)
}

func (a *AuthCookieManager) ClearAccessTokenCookie(w http.ResponseWriter) {
a.clearCookie(w, atCookieName)
}

func (a *AuthCookieManager) SetRefreshTokenCookie(w http.ResponseWriter, rawRefreshToken string) {
a.setCookie(w, rtCookieName, rawRefreshToken, "/", a.userSessionCookieTTL)
}

func (a *AuthCookieManager) GetRefreshTokenCookie(r *http.Request) string {
return a.getCookie(r, rtCookieName)
}

func (a *AuthCookieManager) ClearRefreshTokenCookie(w http.ResponseWriter) {
a.clearCookie(w, rtCookieName)
}

func (a *AuthCookieManager) SetNonceCookie(w http.ResponseWriter, nonce string) {
a.setCookie(w, nonceCookieName, nonce, authCookiePath, a.authCookiesTTL)
}

func (a *AuthCookieManager) GetNonceCookie(r *http.Request) string {
Expand All @@ -37,8 +79,8 @@ func (a *AuthCookieManager) ClearNonceCookie(w http.ResponseWriter) {
a.clearCookie(w, nonceCookieName)
}

func (a *AuthCookieManager) SetStateCookie(w http.ResponseWriter, state string, ttl time.Duration) {
a.setCookie(w, stateCookieName, state, authCookiePath, ttl)
func (a *AuthCookieManager) SetStateCookie(w http.ResponseWriter, state string) {
a.setCookie(w, stateCookieName, state, authCookiePath, a.authCookiesTTL)
}

func (a *AuthCookieManager) GetStateCookie(r *http.Request) string {
Expand All @@ -50,6 +92,10 @@ func (a *AuthCookieManager) ClearStateCookie(w http.ResponseWriter) {
}

func (a *AuthCookieManager) setCookie(w http.ResponseWriter, name, value string, path string, ttl time.Duration) {
if value == "" {
return
}

expires := time.Now().Add(ttl)

encrypted, err := a.encrypt.Encrypt(value)
Expand All @@ -62,14 +108,17 @@ func (a *AuthCookieManager) setCookie(w http.ResponseWriter, name, value string,
Name: name,
Value: encrypted,
Path: path,
Domain: "",
Expires: expires,
MaxAge: int(ttl.Seconds()),
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
}

func (a *AuthCookieManager) clearCookie(w http.ResponseWriter, name string) {
http.SetCookie(w, &http.Cookie{Name: name, Expires: epoch})
http.SetCookie(w, &http.Cookie{Name: name, Expires: epoch, MaxAge: -1})
}

func (a *AuthCookieManager) getCookie(r *http.Request, name string) string {
Expand All @@ -87,9 +136,17 @@ func (a *AuthCookieManager) getCookie(r *http.Request, name string) string {
return value
}

func NewAuthCookieManager(encrypt EncryptInterface, logger logging.LoggerInterface) *AuthCookieManager {
func NewAuthCookieManager(
authCookiesTTLSeconds,
userSessionCookieTTLSeconds int,
encrypt EncryptInterface,
logger logging.LoggerInterface,
) *AuthCookieManager {
a := new(AuthCookieManager)
a.authCookiesTTL = time.Duration(authCookiesTTLSeconds) * time.Second
a.userSessionCookieTTL = time.Duration(userSessionCookieTTLSeconds) * time.Second
a.encrypt = encrypt

a.logger = logger
return a

Expand Down
Loading

0 comments on commit 61d0482

Please sign in to comment.