diff --git a/docs/docs/apis/openidoauth/endpoints.md b/docs/docs/apis/openidoauth/endpoints.md index b0b6351d49..f05cd78f80 100644 --- a/docs/docs/apis/openidoauth/endpoints.md +++ b/docs/docs/apis/openidoauth/endpoints.md @@ -139,6 +139,46 @@ curl --request POST \ --data client_assertion=eyJhbGciOiJSUzI1Ni... ``` +### Refresh Token Grant + +--- + +Required request Parameters + +| Parameter | Description | +| ------------- | ----------------------------------------------------------------------------------- | +| grant_type | Must be `refresh_token` | +| refresh_token | The refresh_token previously issued in the last auth code or refresh token request. | +| scope | [Scopes](Scopes) you would like to request from ZITADEL for the new access_token. Must be a subset of the scope originally requested by the corresponding auth request. When omitted, the scopes requested by the original auth request will be reused. Scopes are space delimited, e.g. `openid email profile` | + +Depending on your authorization method you will have to provide additional parameters or headers: + +When using `client_secret_basic` + +Send your `client_id` and `client_secret` as Basic Auth Header. Check [Client Secret Basic Auth Method](authn-methods#client-secret-basic) on how to build it correctly. + +When using `client_secret_post` + +Send your `client_id` and `client_secret` as parameters in the body: + +| Parameter | Description | +| ------------- | -------------------------------- | +| client_id | client_id of the application | +| client_secret | client_secret of the application | + +When using `none` (PKCE) + +Send your `client_id` as parameter in the body. No authentication is required. + +When using `private_key_jwt` + +Send a client assertion as JWT for us to validate the signature against the registered public key. + +| Parameter | Description | +| --------------------- | --------------------------------------------------------------------------------------------------------------- | +| client_assertion | JWT built and signed according to [Using JWTs for Client Authentication](authn-methods#jwt-with-private-key) | +| client_assertion_type | Must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` | + ## introspection_endpoint [https://api.zitadel.ch/oauth/v2/introspect](https://api.zitadel.ch/oauth/v2/introspect) diff --git a/docs/docs/apis/openidoauth/grant-types.md b/docs/docs/apis/openidoauth/grant-types.md index 6c4d279ffe..6bf781e233 100644 --- a/docs/docs/apis/openidoauth/grant-types.md +++ b/docs/docs/apis/openidoauth/grant-types.md @@ -12,7 +12,7 @@ For a list of supported or unsupported `Grant Types` please have a look at the t | Device Authorization | under consideration | | Implicit | yes | | JSON Web Token (JWT) Profile | yes | -| Refresh Token | work in progress | +| Refresh Token | yes | | Resource Owner Password Credentials | no | | Security Assertion Markup Language (SAML) 2.0 Profile | no | | Token Exchange | work in progress | diff --git a/docs/docs/apis/openidoauth/scopes.md b/docs/docs/apis/openidoauth/scopes.md index f3b43a8e67..ec930d6b30 100644 --- a/docs/docs/apis/openidoauth/scopes.md +++ b/docs/docs/apis/openidoauth/scopes.md @@ -6,12 +6,13 @@ ZITADEL supports the usage of scopes as way of requesting information from the I ## Standard Scopes -| Scopes | Example | Description | -|:--------|:----------|------------------------------------------------------| -| openid | `openid` | When using openid connect this is a mandatory scope | -| profile | `profile` | Optional scope to request the profile of the subject | -| email | `email` | Optional scope to request the email of the subject | -| address | `address` | Optional scope to request the address of the subject | +| Scopes | Example | Description | +|:---------------|:-----------------|--------------------------------------------------------------------------------| +| openid | `openid` | When using openid connect this is a mandatory scope | +| profile | `profile` | Optional scope to request the profile of the subject | +| email | `email` | Optional scope to request the email of the subject | +| address | `address` | Optional scope to request the address of the subject | +| offline_access | `offline_access` | Optional scope to request a refresh_token (only possible when using code flow) | ## Custom Scopes diff --git a/go.mod b/go.mod index 72e178ba69..dae2988f2a 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/allegro/bigcache v1.2.1 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc github.com/caos/logging v0.0.2 - github.com/caos/oidc v0.15.0 + github.com/caos/oidc v0.15.1 github.com/caos/orbos v1.5.14-0.20210428081839-983ffc569980 github.com/cockroachdb/cockroach-go/v2 v2.1.0 github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43 diff --git a/go.sum b/go.sum index 2518ec435d..6e6ef74f4f 100644 --- a/go.sum +++ b/go.sum @@ -137,8 +137,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo= github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= github.com/caos/oidc v0.14.4/go.mod h1:H5Y2zw3YIrWqQOoy0wcmZva2a66bumDyU2iOhXiM9uA= -github.com/caos/oidc v0.15.0 h1:lSykVX6yfUbWpJPAZ9/ZCuowo95h7AgfgPaC15lzf4Y= -github.com/caos/oidc v0.15.0/go.mod h1:JiK5RXSOgag66wiSOMEkS+yS4R46Baz6dGwfr60VfvI= +github.com/caos/oidc v0.15.1 h1:dzMelbk9uYkNTfEy9+273o0fwZTgMSj9eqvS7UtKUpo= +github.com/caos/oidc v0.15.1/go.mod h1:JiK5RXSOgag66wiSOMEkS+yS4R46Baz6dGwfr60VfvI= github.com/caos/orbos v1.5.14-0.20210428081839-983ffc569980 h1:Fz0aYUwGMA2tsu5w7SryqFGjqGClJVHbyhBMT5SXtPU= github.com/caos/orbos v1.5.14-0.20210428081839-983ffc569980/go.mod h1:2I8oiZb5SMRm/qTLvwpSmdV0M6ex8J/UKyxUGfKaqJo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= diff --git a/internal/api/oidc/client_converter.go b/internal/api/oidc/client_converter.go index 6098dd43c7..f23ca9dfd9 100644 --- a/internal/api/oidc/client_converter.go +++ b/internal/api/oidc/client_converter.go @@ -61,6 +61,10 @@ func (c *Client) ResponseTypes() []oidc.ResponseType { return responseTypesToOIDC(c.OIDCResponseTypes) } +func (c *Client) GrantTypes() []oidc.GrantType { + return grantTypesToOIDC(c.OIDCGrantTypes) +} + func (c *Client) DevMode() bool { return c.ApplicationView.DevMode } @@ -165,6 +169,27 @@ func responseTypeToOIDC(responseType model.OIDCResponseType) oidc.ResponseType { } } +func grantTypesToOIDC(grantTypes []model.OIDCGrantType) []oidc.GrantType { + oidcTypes := make([]oidc.GrantType, len(grantTypes)) + for i, t := range grantTypes { + oidcTypes[i] = grantTypeToOIDC(t) + } + return oidcTypes +} + +func grantTypeToOIDC(grantType model.OIDCGrantType) oidc.GrantType { + switch grantType { + case model.OIDCGrantTypeAuthorizationCode: + return oidc.GrantTypeCode + case model.OIDCGrantTypeImplicit: + return oidc.GrantTypeImplicit + case model.OIDCGrantTypeRefreshToken: + return oidc.GrantTypeRefreshToken + default: + return oidc.GrantTypeCode + } +} + func removeScopeWithPrefix(scopes []string, scopePrefix ...string) []string { newScopeList := make([]string, 0) for _, scope := range scopes { diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index af175dd897..e683892881 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -73,6 +73,7 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, command *command.C } copy(config.OPConfig.CryptoKey[:], cryptoKey) config.OPConfig.CodeMethodS256 = true + config.OPConfig.GrantTypeRefreshToken = true metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount} provider, err := op.NewOpenIDProvider( ctx, diff --git a/internal/domain/application_oidc.go b/internal/domain/application_oidc.go index fd67b9c6f6..40b12ff5bc 100644 --- a/internal/domain/application_oidc.go +++ b/internal/domain/application_oidc.go @@ -190,6 +190,7 @@ func GetOIDCV1Compliance(appType OIDCApplicationType, grantTypes []OIDCGrantType compliance.NoneCompliant = true compliance.Problems = append([]string{"Application.OIDC.V1.NoRedirectUris"}, compliance.Problems...) } + CheckGrantTypes(compliance, grantTypes) if containsOIDCGrantType(grantTypes, OIDCGrantTypeImplicit) && containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) { CheckRedirectUrisImplicitAndCode(compliance, appType, redirectUris) } else { @@ -213,6 +214,13 @@ func GetOIDCV1Compliance(appType OIDCApplicationType, grantTypes []OIDCGrantType return compliance } +func CheckGrantTypes(compliance *Compliance, grantTypes []OIDCGrantType) { + if containsOIDCGrantType(grantTypes, OIDCGrantTypeRefreshToken) && !containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode) { + compliance.NoneCompliant = true + compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.GrantType.Refresh.NoAuthCode") + } +} + func GetOIDCV1NativeApplicationCompliance(compliance *Compliance, authMethod OIDCAuthMethodType) { if authMethod != OIDCAuthMethodTypeNone { compliance.NoneCompliant = true @@ -238,7 +246,7 @@ func CheckRedirectUrisCode(compliance *Compliance, appType OIDCApplicationType, } if appType == OIDCApplicationTypeNative && !onlyLocalhostIsHttp(redirectUris) { compliance.NoneCompliant = true - compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.NativeShouldBeHttpLocalhost") + compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost") } } if containsCustom(redirectUris) && appType != OIDCApplicationTypeNative { @@ -259,7 +267,7 @@ func CheckRedirectUrisImplicit(compliance *Compliance, appType OIDCApplicationTy if appType == OIDCApplicationTypeNative { if !onlyLocalhostIsHttp(redirectUris) { compliance.NoneCompliant = true - compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.NativeShouldBeHttpLocalhost") + compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost") } return } @@ -283,7 +291,7 @@ func CheckRedirectUrisImplicitAndCode(compliance *Compliance, appType OIDCApplic } if !onlyLocalhostIsHttp(redirectUris) && appType == OIDCApplicationTypeNative { compliance.NoneCompliant = true - compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.NativeShouldBeHttpLocalhost") + compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost") } } if !compliance.NoneCompliant { diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 1a987aa178..c2617c0540 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -695,11 +695,15 @@ Application: RedirectUris: CustomNotAllowed: Grant Type Implicit erlaubt keine custom Redirect Uris. HttpNotAllowed: Grant Type Implicit erlaubt keine http Redirect Uris. - NativeShouldBeHttpLocalhost: Grant Type Implicit erlaubt beim Apptype Native http nur mit localhost (http://localhost) HttpLocalhostOnlyForNative: Http://localhost Redirect Uri ist nur für Native Applikationen erlaubt. Native: AuthMethodType: NotNone: Bei Native Applikationen sollte der AuthMethodType none sein. + RedirectUris: + MustBeHttpLocalhost: Die Weiterleitung muss mit einem eigenen Protokoll, http://127.0.0.1, http://[::1] oder http://localhost beginnen. UserAgent: AuthMethodType: NotNone: Bei einem User Agent sollte der AuthMethodType none sein. + GrantType: + Refresh: + NoAuthCode: Refresh Token nur in Kombination mit Authorization Code erlaubt. diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 2f2015c520..6f7f6cebb0 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -696,11 +696,15 @@ Application: RedirectUris: CustomNotAllowed: Grant type implicit doesn't allow custom redirect uris HttpNotAllowed: Grant tpye implicit doesn't allow http redirect uris - NativeShouldBeHttpLocalhost: Grant tpye implicit only allowed http://localhost for native apptype HttpLocalhostOnlyForNative: Http://localhost redirect uri is only allowed for native applications. Native: AuthMethodType: NotNone: Native applications should have authmethodtype none. + RedirectUris: + MustBeHttpLocalhost: Redirect URIs must begin with your own protocol, http://127.0.0.1, http://[::1] or http://localhost. UserAgent: AuthMethodType: NotNone: User agent app should have authmethodtype none. + GrantType: + Refresh: + NoAuthCode: Refresh Token only allowed in combination with Authorization Code.