mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-02 08:19:23 +02:00
authorize: support authenticating with idp tokens (#5484)
* identity: add support for verifying access and identity tokens * allow overriding with policy option * authenticate: add verify endpoints * wip * implement session creation * add verify test * implement idp token login * fix tests * add pr permission * make session ids route-specific * rename method * add test * add access token test * test for newUserFromIDPClaims * more tests * make the session id per-idp * use type for * add test * remove nil checks
This commit is contained in:
parent
6e22b7a19a
commit
b9fd926618
36 changed files with 2791 additions and 885 deletions
|
@ -43,6 +43,16 @@ func (a *Authenticate) Handler() http.Handler {
|
|||
func (a *Authenticate) Mount(r *mux.Router) {
|
||||
r.StrictSlash(true)
|
||||
r.Use(middleware.SetHeaders(httputil.HeadersContentSecurityPolicy))
|
||||
// disable csrf checking for these endpoints
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/.pomerium/verify-access-token" ||
|
||||
r.URL.Path == "/.pomerium/verify-identity-token" {
|
||||
r = csrf.UnsafeSkipCheck(r)
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
r.Use(func(h http.Handler) http.Handler {
|
||||
options := a.options.Load()
|
||||
state := a.state.Load()
|
||||
|
@ -95,6 +105,8 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
|
|||
// routes that don't need a session:
|
||||
sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
|
||||
sr.Path("/signed_out").Handler(httputil.HandlerFunc(a.signedOut)).Methods(http.MethodGet)
|
||||
sr.Path("/verify-access-token").Handler(httputil.HandlerFunc(a.verifyAccessToken)).Methods(http.MethodPost)
|
||||
sr.Path("/verify-identity-token").Handler(httputil.HandlerFunc(a.verifyIdentityToken)).Methods(http.MethodPost)
|
||||
|
||||
// routes that need a session:
|
||||
sr = sr.NewRoute().Subrouter()
|
||||
|
|
76
authenticate/handlers_verify.go
Normal file
76
authenticate/handlers_verify.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package authenticate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/pkg/authenticateapi"
|
||||
)
|
||||
|
||||
func (a *Authenticate) verifyAccessToken(w http.ResponseWriter, r *http.Request) error {
|
||||
var req authenticateapi.VerifyAccessTokenRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return httputil.NewError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
authenticator, err := a.cfg.getIdentityProvider(r.Context(), a.tracerProvider, a.options.Load(), req.IdentityProviderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var res authenticateapi.VerifyTokenResponse
|
||||
claims, err := authenticator.VerifyAccessToken(r.Context(), req.AccessToken)
|
||||
if err == nil {
|
||||
res.Valid = true
|
||||
res.Claims = claims
|
||||
} else {
|
||||
res.Valid = false
|
||||
log.Ctx(r.Context()).Info().
|
||||
Err(err).
|
||||
Str("idp", authenticator.Name()).
|
||||
Msg("access token failed verification")
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(&res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Authenticate) verifyIdentityToken(w http.ResponseWriter, r *http.Request) error {
|
||||
var req authenticateapi.VerifyIdentityTokenRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
return httputil.NewError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
authenticator, err := a.cfg.getIdentityProvider(r.Context(), a.tracerProvider, a.options.Load(), req.IdentityProviderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var res authenticateapi.VerifyTokenResponse
|
||||
claims, err := authenticator.VerifyIdentityToken(r.Context(), req.IdentityToken)
|
||||
if err == nil {
|
||||
res.Valid = true
|
||||
res.Claims = claims
|
||||
} else {
|
||||
res.Valid = false
|
||||
log.Ctx(r.Context()).Info().
|
||||
Err(err).
|
||||
Str("idp", authenticator.Name()).
|
||||
Msg("identity token failed verification")
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(&res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
45
authenticate/handlers_verify_test.go
Normal file
45
authenticate/handlers_verify_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package authenticate_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/pomerium/pomerium/authenticate"
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/testutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
)
|
||||
|
||||
func TestVerifyAccessToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.GetContext(t, time.Minute)
|
||||
a, err := authenticate.New(ctx, &config.Config{
|
||||
Options: &config.Options{
|
||||
CookieSecret: cryptutil.NewBase64Key(),
|
||||
SharedKey: cryptutil.NewBase64Key(),
|
||||
AuthenticateCallbackPath: "/oauth2/callback",
|
||||
AuthenticateURLString: "https://authenticate.example.com",
|
||||
|
||||
Provider: "oidc",
|
||||
ProviderURL: "http://oidc.example.com",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://authenticate.example.com/.pomerium/verify-access-token",
|
||||
strings.NewReader(`{"accessToken":"ACCESS TOKEN"}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
a.Handler().ServeHTTP(w, r)
|
||||
|
||||
assert.Equal(t, 200, w.Code)
|
||||
assert.JSONEq(t, `{"valid":false}`, w.Body.String())
|
||||
}
|
|
@ -3,11 +3,12 @@ package authenticate
|
|||
import (
|
||||
"context"
|
||||
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/pkg/identity"
|
||||
"github.com/pomerium/pomerium/pkg/identity/oauth"
|
||||
oteltrace "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.TracerProvider, options *config.Options, idpID string) (identity.Authenticator, error) {
|
||||
|
@ -26,7 +27,8 @@ func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.Tr
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return identity.NewAuthenticator(ctx, tracerProvider, oauth.Options{
|
||||
|
||||
o := oauth.Options{
|
||||
RedirectURL: redirectURL,
|
||||
ProviderName: idp.GetType(),
|
||||
ProviderURL: idp.GetUrl(),
|
||||
|
@ -34,5 +36,9 @@ func defaultGetIdentityProvider(ctx context.Context, tracerProvider oteltrace.Tr
|
|||
ClientSecret: idp.GetClientSecret(),
|
||||
Scopes: idp.GetScopes(),
|
||||
AuthCodeOptions: idp.GetRequestParams(),
|
||||
})
|
||||
}
|
||||
if v := idp.GetAccessTokenAllowedAudiences(); v != nil {
|
||||
o.AccessTokenAllowedAudiences = &v.Values
|
||||
}
|
||||
return identity.NewAuthenticator(ctx, tracerProvider, o)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue