diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.forgejo.bak/ISSUE_TEMPLATE/bug_report.yaml
similarity index 99%
rename from .github/ISSUE_TEMPLATE/bug_report.yaml
rename to .forgejo.bak/ISSUE_TEMPLATE/bug_report.yaml
index 92465f9..d024341 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.forgejo.bak/ISSUE_TEMPLATE/bug_report.yaml
@@ -2,6 +2,7 @@ name: Bug Report
description: "Create a bug report to help us improve ZITADEL. Click [here](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#product-management) to see how we process your issue."
title: "[Bug]: "
labels: ["bug"]
+type: Bug
body:
- type: markdown
attributes:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.forgejo.bak/ISSUE_TEMPLATE/config.yml
similarity index 100%
rename from .github/ISSUE_TEMPLATE/config.yml
rename to .forgejo.bak/ISSUE_TEMPLATE/config.yml
diff --git a/.github/ISSUE_TEMPLATE/docs.yaml b/.forgejo.bak/ISSUE_TEMPLATE/docs.yaml
similarity index 98%
rename from .github/ISSUE_TEMPLATE/docs.yaml
rename to .forgejo.bak/ISSUE_TEMPLATE/docs.yaml
index 04c1c0c..d3f82b9 100644
--- a/.github/ISSUE_TEMPLATE/docs.yaml
+++ b/.forgejo.bak/ISSUE_TEMPLATE/docs.yaml
@@ -1,6 +1,7 @@
name: 📄 Documentation
description: Create an issue for missing or wrong documentation.
labels: ["docs"]
+type: task
body:
- type: markdown
attributes:
diff --git a/.github/ISSUE_TEMPLATE/improvement.yaml b/.forgejo.bak/ISSUE_TEMPLATE/enhancement.yaml
similarity index 92%
rename from .github/ISSUE_TEMPLATE/improvement.yaml
rename to .forgejo.bak/ISSUE_TEMPLATE/enhancement.yaml
index 2e2ddf4..ef2103e 100644
--- a/.github/ISSUE_TEMPLATE/improvement.yaml
+++ b/.forgejo.bak/ISSUE_TEMPLATE/enhancement.yaml
@@ -1,11 +1,12 @@
name: 🛠️ Improvement
description: "Create an new issue for an improvment in ZITADEL"
-labels: ["improvement"]
+labels: ["enhancement"]
+type: enhancement
body:
- type: markdown
attributes:
value: |
- Thanks for taking the time to fill out this improvement request
+ Thanks for taking the time to fill out this proposal / feature reqeust
- type: checkboxes
id: preflight
attributes:
diff --git a/.github/dependabot.yml b/.forgejo.bak/dependabot.yml
similarity index 100%
rename from .github/dependabot.yml
rename to .forgejo.bak/dependabot.yml
diff --git a/.github/pull_request_template.md b/.forgejo.bak/pull_request_template.md
similarity index 100%
rename from .github/pull_request_template.md
rename to .forgejo.bak/pull_request_template.md
diff --git a/.github/workflows/codeql-analysis.yml b/.forgejo.bak/workflows/codeql-analysis.yml
similarity index 100%
rename from .github/workflows/codeql-analysis.yml
rename to .forgejo.bak/workflows/codeql-analysis.yml
diff --git a/.github/workflows/issue.yml b/.forgejo.bak/workflows/issue.yml
similarity index 100%
rename from .github/workflows/issue.yml
rename to .forgejo.bak/workflows/issue.yml
diff --git a/.github/workflows/release.yml b/.forgejo.bak/workflows/release.yml
similarity index 89%
rename from .github/workflows/release.yml
rename to .forgejo.bak/workflows/release.yml
index 48690cf..00063e4 100644
--- a/.github/workflows/release.yml
+++ b/.forgejo.bak/workflows/release.yml
@@ -14,11 +14,11 @@ on:
jobs:
test:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
- go: ['1.21', '1.22']
+ go: ['1.23', '1.24']
name: Go ${{ matrix.go }} test
steps:
- uses: actions/checkout@v4
@@ -27,12 +27,12 @@ jobs:
with:
go-version: ${{ matrix.go }}
- run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/...
- - uses: codecov/codecov-action@v4.5.0
+ - uses: codecov/codecov-action@v5.4.3
with:
file: ./profile.cov
name: codecov-go
release:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
needs: [test]
if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }}
env:
diff --git a/.github/ISSUE_TEMPLATE/proposal.yaml b/.github/ISSUE_TEMPLATE/proposal.yaml
deleted file mode 100644
index af7acd5..0000000
--- a/.github/ISSUE_TEMPLATE/proposal.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-name: 💡 Proposal / Feature request
-description: "Create an issue for a feature request/proposal."
-labels: ["enhancement"]
-body:
- - type: markdown
- attributes:
- value: |
- Thanks for taking the time to fill out this proposal / feature reqeust
- - type: checkboxes
- id: preflight
- attributes:
- label: Preflight Checklist
- options:
- - label:
- I could not find a solution in the existing issues, docs, nor discussions
- required: true
- - label:
- I have joined the [ZITADEL chat](https://zitadel.com/chat)
- - type: textarea
- id: problem
- attributes:
- label: Describe your problem
- description: Please describe your problem this proposal / feature is supposed to solve.
- placeholder: Describe the problem you have.
- validations:
- required: true
- - type: textarea
- id: solution
- attributes:
- label: Describe your ideal solution
- description: Which solution do you propose?
- placeholder: As a [type of user], I want [some goal] so that [some reason].
- validations:
- required: true
- - type: input
- id: version
- attributes:
- label: Version
- description: Which version of the OIDC Library are you using.
- - type: textarea
- id: additional
- attributes:
- label: Additional Context
- description: Please add any other infos that could be useful.
diff --git a/README.md b/README.md
index 01d7d47..bc346f5 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,10 @@ Whenever possible we tried to reuse / extend existing packages like `OAuth2 for
## Basic Overview
The most important packages of the library:
+
/pkg
- /client clients using the OP for retrieving, exchanging and verifying tokens
+ /client clients using the OP for retrieving, exchanging and verifying tokens
/rp definition and implementation of an OIDC Relying Party (client)
/rs definition and implementation of an OAuth Resource Server (API)
/op definition and implementation of an OIDC OpenID Provider (server)
@@ -37,7 +38,6 @@ The most important packages of the library:
/server examples of an OpenID Provider implementations (including dynamic) with some very basic login UI
-
### Semver
This package uses [semver](https://semver.org/) for [releases](https://github.com/zitadel/oidc/releases). Major releases ship breaking changes. Starting with the `v2` to `v3` increment we provide an [upgrade guide](UPGRADING.md) to ease migration to a newer version.
@@ -55,48 +55,84 @@ CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid
```
- open http://localhost:9999/login in your browser
-- you will be redirected to op server and the login UI
+- you will be redirected to op server and the login UI
- login with user `test-user@localhost` and password `verysecure`
- the OP will redirect you to the client app, which displays the user info
for the dynamic issuer, just start it with:
+
```bash
go run github.com/zitadel/oidc/v3/example/server/dynamic
-```
+```
+
the oidc web client above will still work, but if you add `oidc.local` (pointing to 127.0.0.1) in your hosts file you can also start it with:
+
```bash
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app
```
> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`)
+### Server configuration
+
+Example server allows extra configuration using environment variables and could be used for end to
+end testing of your services.
+
+| Name | Format | Description |
+| ------------ | -------------------------------- | ------------------------------------- |
+| PORT | Number between 1 and 65535 | OIDC listen port |
+| REDIRECT_URI | Comma-separated URIs | List of allowed redirect URIs |
+| USERS_FILE | Path to json in local filesystem | Users with their data and credentials |
+
+Here is json equivalent for one of the default users
+
+```json
+{
+ "id2": {
+ "ID": "id2",
+ "Username": "test-user2",
+ "Password": "verysecure",
+ "FirstName": "Test",
+ "LastName": "User2",
+ "Email": "test-user2@zitadel.ch",
+ "EmailVerified": true,
+ "Phone": "",
+ "PhoneVerified": false,
+ "PreferredLanguage": "DE",
+ "IsAdmin": false
+ }
+}
+```
+
## Features
-| | Relying party | OpenID Provider | Specification |
-| -------------------- | ------------- | --------------- | ----------------------------------------- |
-| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] |
-| Implicit Flow | no[^1] | yes | OpenID Connect Core 1.0, [Section 3.2][2] |
-| Hybrid Flow | no | not yet | OpenID Connect Core 1.0, [Section 3.3][3] |
-| Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] |
-| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] |
-| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 |
-| JWT Profile | yes | yes | [RFC 7523][7] |
-| PKCE | yes | yes | [RFC 7636][8] |
-| Token Exchange | yes | yes | [RFC 8693][9] |
-| Device Authorization | yes | yes | [RFC 8628][10] |
-| mTLS | not yet | not yet | [RFC 8705][11] |
+| | Relying party | OpenID Provider | Specification |
+| -------------------- | ------------- | --------------- | -------------------------------------------- |
+| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] |
+| Implicit Flow | no[^1] | yes | OpenID Connect Core 1.0, [Section 3.2][2] |
+| Hybrid Flow | no | not yet | OpenID Connect Core 1.0, [Section 3.3][3] |
+| Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] |
+| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] |
+| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 |
+| JWT Profile | yes | yes | [RFC 7523][7] |
+| PKCE | yes | yes | [RFC 7636][8] |
+| Token Exchange | yes | yes | [RFC 8693][9] |
+| Device Authorization | yes | yes | [RFC 8628][10] |
+| mTLS | not yet | not yet | [RFC 8705][11] |
+| Back-Channel Logout | not yet | yes | OpenID Connect [Back-Channel Logout][12] 1.0 |
-[1]: "3.1. Authentication using the Authorization Code Flow"
-[2]: "3.2. Authentication using the Implicit Flow"
-[3]: "3.3. Authentication using the Hybrid Flow"
-[4]: "9. Client Authentication"
-[5]: "12. Using Refresh Tokens"
-[6]: "OpenID Connect Discovery 1.0 incorporating errata set 1"
-[7]: "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants"
-[8]: "Proof Key for Code Exchange by OAuth Public Clients"
-[9]: "OAuth 2.0 Token Exchange"
-[10]: "OAuth 2.0 Device Authorization Grant"
-[11]: "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens"
+[1]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth "3.1. Authentication using the Authorization Code Flow"
+[2]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth "3.2. Authentication using the Implicit Flow"
+[3]: https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth "3.3. Authentication using the Hybrid Flow"
+[4]: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication "9. Client Authentication"
+[5]: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens "12. Using Refresh Tokens"
+[6]: https://openid.net/specs/openid-connect-discovery-1_0.html "OpenID Connect Discovery 1.0 incorporating errata set 1"
+[7]: https://www.rfc-editor.org/rfc/rfc7523.html "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants"
+[8]: https://www.rfc-editor.org/rfc/rfc7636.html "Proof Key for Code Exchange by OAuth Public Clients"
+[9]: https://www.rfc-editor.org/rfc/rfc8693.html "OAuth 2.0 Token Exchange"
+[10]: https://www.rfc-editor.org/rfc/rfc8628.html "OAuth 2.0 Device Authorization Grant"
+[11]: https://www.rfc-editor.org/rfc/rfc8705.html "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens"
+[12]: https://openid.net/specs/openid-connect-backchannel-1_0.html "OpenID Connect Back-Channel Logout 1.0 incorporating errata set 1"
## Contributors
@@ -115,14 +151,14 @@ For your convenience you can find the relevant guides linked below.
## Supported Go Versions
-For security reasons, we only support and recommend the use of one of the latest two Go versions (:white_check_mark:).
+For security reasons, we only support and recommend the use of one of the latest two Go versions (:white_check_mark:).
Versions that also build are marked with :warning:.
| Version | Supported |
| ------- | ------------------ |
-| <1.21 | :x: |
-| 1.21 | :white_check_mark: |
-| 1.22 | :white_check_mark: |
+| <1.23 | :x: |
+| 1.23 | :white_check_mark: |
+| 1.24 | :white_check_mark: |
## Why another library
@@ -153,5 +189,4 @@ Unless required by applicable law or agreed to in writing, software distributed
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
-
[^1]: https://github.com/zitadel/oidc/issues/135#issuecomment-950563892
diff --git a/example/client/api/api.go b/example/client/api/api.go
index 2e61c21..69f9466 100644
--- a/example/client/api/api.go
+++ b/example/client/api/api.go
@@ -13,8 +13,8 @@ import (
"github.com/go-chi/chi/v5"
"github.com/sirupsen/logrus"
- "github.com/zitadel/oidc/v3/pkg/client/rs"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
const (
diff --git a/example/client/app/app.go b/example/client/app/app.go
index 0b9b19d..90b1969 100644
--- a/example/client/app/app.go
+++ b/example/client/app/app.go
@@ -14,10 +14,10 @@ import (
"github.com/google/uuid"
"github.com/sirupsen/logrus"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/zitadel/logging"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
var (
diff --git a/example/client/device/device.go b/example/client/device/device.go
index 78ed2c8..33bc570 100644
--- a/example/client/device/device.go
+++ b/example/client/device/device.go
@@ -45,8 +45,8 @@ import (
"github.com/sirupsen/logrus"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
)
var (
diff --git a/example/client/github/github.go b/example/client/github/github.go
index 7d069d4..f6c536b 100644
--- a/example/client/github/github.go
+++ b/example/client/github/github.go
@@ -10,10 +10,10 @@ import (
"golang.org/x/oauth2"
githubOAuth "golang.org/x/oauth2/github"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- "github.com/zitadel/oidc/v3/pkg/client/rp/cli"
- "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp/cli"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
var (
diff --git a/example/client/service/service.go b/example/client/service/service.go
index 865a4e0..a88ab2f 100644
--- a/example/client/service/service.go
+++ b/example/client/service/service.go
@@ -13,7 +13,7 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/v3/pkg/client/profile"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/profile"
)
var client = http.DefaultClient
diff --git a/example/server/config/config.go b/example/server/config/config.go
new file mode 100644
index 0000000..96837d4
--- /dev/null
+++ b/example/server/config/config.go
@@ -0,0 +1,40 @@
+package config
+
+import (
+ "os"
+ "strings"
+)
+
+const (
+ // default port for the http server to run
+ DefaultIssuerPort = "9998"
+)
+
+type Config struct {
+ Port string
+ RedirectURI []string
+ UsersFile string
+}
+
+// FromEnvVars loads configuration parameters from environment variables.
+// If there is no such variable defined, then use default values.
+func FromEnvVars(defaults *Config) *Config {
+ if defaults == nil {
+ defaults = &Config{}
+ }
+ cfg := &Config{
+ Port: defaults.Port,
+ RedirectURI: defaults.RedirectURI,
+ UsersFile: defaults.UsersFile,
+ }
+ if value, ok := os.LookupEnv("PORT"); ok {
+ cfg.Port = value
+ }
+ if value, ok := os.LookupEnv("USERS_FILE"); ok {
+ cfg.UsersFile = value
+ }
+ if value, ok := os.LookupEnv("REDIRECT_URI"); ok {
+ cfg.RedirectURI = strings.Split(value, ",")
+ }
+ return cfg
+}
diff --git a/example/server/config/config_test.go b/example/server/config/config_test.go
new file mode 100644
index 0000000..3b73c0b
--- /dev/null
+++ b/example/server/config/config_test.go
@@ -0,0 +1,77 @@
+package config
+
+import (
+ "fmt"
+ "os"
+ "testing"
+)
+
+func TestFromEnvVars(t *testing.T) {
+
+ for _, tc := range []struct {
+ name string
+ env map[string]string
+ defaults *Config
+ want *Config
+ }{
+ {
+ name: "no vars, no default values",
+ env: map[string]string{},
+ want: &Config{},
+ },
+ {
+ name: "no vars, only defaults",
+ env: map[string]string{},
+ defaults: &Config{
+ Port: "6666",
+ UsersFile: "/default/user/path",
+ RedirectURI: []string{"re", "direct", "uris"},
+ },
+ want: &Config{
+ Port: "6666",
+ UsersFile: "/default/user/path",
+ RedirectURI: []string{"re", "direct", "uris"},
+ },
+ },
+ {
+ name: "overriding default values",
+ env: map[string]string{
+ "PORT": "1234",
+ "USERS_FILE": "/path/to/users",
+ "REDIRECT_URI": "http://redirect/redirect",
+ },
+ defaults: &Config{
+ Port: "6666",
+ UsersFile: "/default/user/path",
+ RedirectURI: []string{"re", "direct", "uris"},
+ },
+ want: &Config{
+ Port: "1234",
+ UsersFile: "/path/to/users",
+ RedirectURI: []string{"http://redirect/redirect"},
+ },
+ },
+ {
+ name: "multiple redirect uris",
+ env: map[string]string{
+ "REDIRECT_URI": "http://host_1,http://host_2,http://host_3",
+ },
+ want: &Config{
+ RedirectURI: []string{
+ "http://host_1", "http://host_2", "http://host_3",
+ },
+ },
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ os.Clearenv()
+ for k, v := range tc.env {
+ os.Setenv(k, v)
+ }
+ cfg := FromEnvVars(tc.defaults)
+ if fmt.Sprint(cfg) != fmt.Sprint(tc.want) {
+ t.Errorf("Expected FromEnvVars()=%q, but got %q", tc.want, cfg)
+ }
+ })
+ }
+}
diff --git a/example/server/dynamic/login.go b/example/server/dynamic/login.go
index 685b444..05f0e34 100644
--- a/example/server/dynamic/login.go
+++ b/example/server/dynamic/login.go
@@ -8,7 +8,7 @@ import (
"github.com/go-chi/chi/v5"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
const (
diff --git a/example/server/dynamic/op.go b/example/server/dynamic/op.go
index 432a575..2c00e41 100644
--- a/example/server/dynamic/op.go
+++ b/example/server/dynamic/op.go
@@ -10,8 +10,8 @@ import (
"github.com/go-chi/chi/v5"
"golang.org/x/text/language"
- "github.com/zitadel/oidc/v3/example/server/storage"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
const (
diff --git a/example/server/exampleop/device.go b/example/server/exampleop/device.go
index 2f9be52..99505e4 100644
--- a/example/server/exampleop/device.go
+++ b/example/server/exampleop/device.go
@@ -8,10 +8,10 @@ import (
"net/http"
"net/url"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/go-chi/chi/v5"
"github.com/gorilla/securecookie"
"github.com/sirupsen/logrus"
- "github.com/zitadel/oidc/v3/pkg/op"
)
type deviceAuthenticate interface {
diff --git a/example/server/exampleop/login.go b/example/server/exampleop/login.go
index 4d2b478..77a6189 100644
--- a/example/server/exampleop/login.go
+++ b/example/server/exampleop/login.go
@@ -5,8 +5,8 @@ import (
"fmt"
"net/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/go-chi/chi/v5"
- "github.com/zitadel/oidc/v3/pkg/op"
)
type login struct {
diff --git a/example/server/exampleop/op.go b/example/server/exampleop/op.go
index e8ef892..e12c755 100644
--- a/example/server/exampleop/op.go
+++ b/example/server/exampleop/op.go
@@ -12,22 +12,13 @@ import (
"github.com/zitadel/logging"
"golang.org/x/text/language"
- "github.com/zitadel/oidc/v3/example/server/storage"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
const (
pathLoggedOut = "/logged-out"
)
-func init() {
- storage.RegisterClients(
- storage.NativeClient("native"),
- storage.WebClient("web", "secret"),
- storage.WebClient("api", "secret"),
- )
-}
-
type Storage interface {
op.Storage
authenticate
diff --git a/example/server/main.go b/example/server/main.go
index a2ad190..5bdbb05 100644
--- a/example/server/main.go
+++ b/example/server/main.go
@@ -6,36 +6,53 @@ import (
"net/http"
"os"
- "github.com/zitadel/oidc/v3/example/server/exampleop"
- "github.com/zitadel/oidc/v3/example/server/storage"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/config"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
)
+func getUserStore(cfg *config.Config) (storage.UserStore, error) {
+ if cfg.UsersFile == "" {
+ return storage.NewUserStore(fmt.Sprintf("http://localhost:%s/", cfg.Port)), nil
+ }
+ return storage.StoreFromFile(cfg.UsersFile)
+}
+
func main() {
- //we will run on :9998
- port := "9998"
- //which gives us the issuer: http://localhost:9998/
- issuer := fmt.Sprintf("http://localhost:%s/", port)
-
- // the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations
- // this might be the layer for accessing your database
- // in this example it will be handled in-memory
- storage := storage.NewStorage(storage.NewUserStore(issuer))
-
+ cfg := config.FromEnvVars(&config.Config{Port: "9998"})
logger := slog.New(
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
}),
)
+
+ //which gives us the issuer: http://localhost:9998/
+ issuer := fmt.Sprintf("http://localhost:%s/", cfg.Port)
+
+ storage.RegisterClients(
+ storage.NativeClient("native", cfg.RedirectURI...),
+ storage.WebClient("web", "secret", cfg.RedirectURI...),
+ storage.WebClient("api", "secret", cfg.RedirectURI...),
+ )
+
+ // the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations
+ // this might be the layer for accessing your database
+ // in this example it will be handled in-memory
+ store, err := getUserStore(cfg)
+ if err != nil {
+ logger.Error("cannot create UserStore", "error", err)
+ os.Exit(1)
+ }
+ storage := storage.NewStorage(store)
router := exampleop.SetupServer(issuer, storage, logger, false)
server := &http.Server{
- Addr: ":" + port,
+ Addr: ":" + cfg.Port,
Handler: router,
}
- logger.Info("server listening, press ctrl+c to stop", "addr", fmt.Sprintf("http://localhost:%s/", port))
- err := server.ListenAndServe()
- if err != http.ErrServerClosed {
+ logger.Info("server listening, press ctrl+c to stop", "addr", issuer)
+ if server.ListenAndServe() != http.ErrServerClosed {
logger.Error("server terminated", "error", err)
os.Exit(1)
}
diff --git a/example/server/storage/client.go b/example/server/storage/client.go
index 010b9ce..2b836c0 100644
--- a/example/server/storage/client.go
+++ b/example/server/storage/client.go
@@ -3,8 +3,8 @@ package storage
import (
"time"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
var (
diff --git a/example/server/storage/oidc.go b/example/server/storage/oidc.go
index 9cd08d9..9c7f544 100644
--- a/example/server/storage/oidc.go
+++ b/example/server/storage/oidc.go
@@ -6,8 +6,8 @@ import (
"golang.org/x/text/language"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
const (
@@ -121,7 +121,7 @@ func (a *AuthRequest) Done() bool {
}
func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string {
- prompts := make([]string, len(oidcPrompt))
+ prompts := make([]string, 0, len(oidcPrompt))
for _, oidcPrompt := range oidcPrompt {
switch oidcPrompt {
case oidc.PromptNone,
@@ -164,6 +164,15 @@ func authRequestToInternal(authReq *oidc.AuthRequest, userID string) *AuthReques
}
}
+type AuthRequestWithSessionState struct {
+ *AuthRequest
+ SessionState string
+}
+
+func (a *AuthRequestWithSessionState) GetSessionState() string {
+ return a.SessionState
+}
+
type OIDCCodeChallenge struct {
Challenge string
Method string
diff --git a/example/server/storage/storage.go b/example/server/storage/storage.go
index d8b7a5d..d4315c6 100644
--- a/example/server/storage/storage.go
+++ b/example/server/storage/storage.go
@@ -14,8 +14,8 @@ import (
jose "github.com/go-jose/go-jose/v4"
"github.com/google/uuid"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
@@ -151,6 +151,9 @@ func (s *Storage) CheckUsernamePassword(username, password, id string) error {
// in this example we'll simply check the username / password and set a boolean to true
// therefore we will also just check this boolean if the request / login has been finished
request.done = true
+
+ request.authTime = time.Now()
+
return nil
}
return fmt.Errorf("username or password wrong")
@@ -295,15 +298,19 @@ func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.T
// if we get here, the currentRefreshToken was not empty, so the call is a refresh token request
// we therefore will have to check the currentRefreshToken and renew the refresh token
- refreshToken, refreshTokenID, err := s.renewRefreshToken(currentRefreshToken)
+
+ newRefreshToken = uuid.NewString()
+
+ accessToken, err := s.accessToken(applicationID, newRefreshToken, request.GetSubject(), request.GetAudience(), request.GetScopes())
if err != nil {
return "", "", time.Time{}, err
}
- accessToken, err := s.accessToken(applicationID, refreshTokenID, request.GetSubject(), request.GetAudience(), request.GetScopes())
- if err != nil {
+
+ if err := s.renewRefreshToken(currentRefreshToken, newRefreshToken, accessToken.ID); err != nil {
return "", "", time.Time{}, err
}
- return accessToken.ID, refreshToken, accessToken.Expiration, nil
+
+ return accessToken.ID, newRefreshToken, accessToken.Expiration, nil
}
func (s *Storage) exchangeRefreshToken(ctx context.Context, request op.TokenExchangeRequest) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) {
@@ -385,14 +392,9 @@ func (s *Storage) RevokeToken(ctx context.Context, tokenIDOrToken string, userID
if refreshToken.ApplicationID != clientID {
return oidc.ErrInvalidClient().WithDescription("token was not issued for this client")
}
- // if it is a refresh token, you will have to remove the access token as well
delete(s.refreshTokens, refreshToken.ID)
- for _, accessToken := range s.tokens {
- if accessToken.RefreshTokenID == refreshToken.ID {
- delete(s.tokens, accessToken.ID)
- return nil
- }
- }
+ // if it is a refresh token, you will have to remove the access token as well
+ delete(s.tokens, refreshToken.AccessToken)
return nil
}
@@ -488,6 +490,9 @@ func (s *Storage) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserI
// return err
// }
//}
+ if token.Expiration.Before(time.Now()) {
+ return fmt.Errorf("token is expired")
+ }
return s.setUserinfo(ctx, userinfo, token.Subject, token.ApplicationID, token.Scopes)
}
@@ -594,33 +599,41 @@ func (s *Storage) createRefreshToken(accessToken *Token, amr []string, authTime
Audience: accessToken.Audience,
Expiration: time.Now().Add(5 * time.Hour),
Scopes: accessToken.Scopes,
+ AccessToken: accessToken.ID,
}
s.refreshTokens[token.ID] = token
return token.Token, nil
}
// renewRefreshToken checks the provided refresh_token and creates a new one based on the current
-func (s *Storage) renewRefreshToken(currentRefreshToken string) (string, string, error) {
+//
+// [Refresh Token Rotation] is implemented.
+//
+// [Refresh Token Rotation]: https://www.rfc-editor.org/rfc/rfc6819#section-5.2.2.3
+func (s *Storage) renewRefreshToken(currentRefreshToken, newRefreshToken, newAccessToken string) error {
s.lock.Lock()
defer s.lock.Unlock()
refreshToken, ok := s.refreshTokens[currentRefreshToken]
if !ok {
- return "", "", fmt.Errorf("invalid refresh token")
+ return fmt.Errorf("invalid refresh token")
}
- // deletes the refresh token and all access tokens which were issued based on this refresh token
+ // deletes the refresh token
delete(s.refreshTokens, currentRefreshToken)
- for _, token := range s.tokens {
- if token.RefreshTokenID == currentRefreshToken {
- delete(s.tokens, token.ID)
- break
- }
+
+ // delete the access token which was issued based on this refresh token
+ delete(s.tokens, refreshToken.AccessToken)
+
+ if refreshToken.Expiration.Before(time.Now()) {
+ return fmt.Errorf("expired refresh token")
}
+
// creates a new refresh token based on the current one
- token := uuid.NewString()
- refreshToken.Token = token
- refreshToken.ID = token
- s.refreshTokens[token] = refreshToken
- return token, refreshToken.ID, nil
+ refreshToken.Token = newRefreshToken
+ refreshToken.ID = newRefreshToken
+ refreshToken.Expiration = time.Now().Add(5 * time.Hour)
+ refreshToken.AccessToken = newAccessToken
+ s.refreshTokens[newRefreshToken] = refreshToken
+ return nil
}
// accessToken will store an access_token in-memory based on the provided information
diff --git a/example/server/storage/storage_dynamic.go b/example/server/storage/storage_dynamic.go
index d112d71..765d29a 100644
--- a/example/server/storage/storage_dynamic.go
+++ b/example/server/storage/storage_dynamic.go
@@ -6,8 +6,8 @@ import (
jose "github.com/go-jose/go-jose/v4"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
type multiStorage struct {
diff --git a/example/server/storage/token.go b/example/server/storage/token.go
index ad907e3..beab38c 100644
--- a/example/server/storage/token.go
+++ b/example/server/storage/token.go
@@ -22,4 +22,5 @@ type RefreshToken struct {
ApplicationID string
Expiration time.Time
Scopes []string
+ AccessToken string // Token.ID
}
diff --git a/example/server/storage/user.go b/example/server/storage/user.go
index 173daef..ed8cdfa 100644
--- a/example/server/storage/user.go
+++ b/example/server/storage/user.go
@@ -2,6 +2,8 @@ package storage
import (
"crypto/rsa"
+ "encoding/json"
+ "os"
"strings"
"golang.org/x/text/language"
@@ -35,6 +37,18 @@ type userStore struct {
users map[string]*User
}
+func StoreFromFile(path string) (UserStore, error) {
+ users := map[string]*User{}
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ if err := json.Unmarshal(data, &users); err != nil {
+ return nil, err
+ }
+ return userStore{users}, nil
+}
+
func NewUserStore(issuer string) UserStore {
hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0]
return userStore{
diff --git a/example/server/storage/user_test.go b/example/server/storage/user_test.go
new file mode 100644
index 0000000..c2e2212
--- /dev/null
+++ b/example/server/storage/user_test.go
@@ -0,0 +1,70 @@
+package storage
+
+import (
+ "os"
+ "path"
+ "reflect"
+ "testing"
+
+ "golang.org/x/text/language"
+)
+
+func TestStoreFromFile(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ pathToFile string
+ content string
+ want UserStore
+ wantErr bool
+ }{
+ {
+ name: "normal user file",
+ pathToFile: "userfile.json",
+ content: `{
+ "id1": {
+ "ID": "id1",
+ "EmailVerified": true,
+ "PreferredLanguage": "DE"
+ }
+ }`,
+ want: userStore{map[string]*User{
+ "id1": {
+ ID: "id1",
+ EmailVerified: true,
+ PreferredLanguage: language.German,
+ },
+ }},
+ },
+ {
+ name: "malformed file",
+ pathToFile: "whatever",
+ content: "not a json just a text",
+ wantErr: true,
+ },
+ {
+ name: "not existing file",
+ pathToFile: "what/ever/file",
+ wantErr: true,
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ actualPath := path.Join(t.TempDir(), tc.pathToFile)
+
+ if tc.content != "" && tc.pathToFile != "" {
+ if err := os.WriteFile(actualPath, []byte(tc.content), 0666); err != nil {
+ t.Fatalf("cannot create file with test content: %q", tc.content)
+ }
+ }
+ result, err := StoreFromFile(actualPath)
+ if err != nil && !tc.wantErr {
+ t.Errorf("StoreFromFile(%q) returned unexpected error %q", tc.pathToFile, err)
+ } else if err == nil && tc.wantErr {
+ t.Errorf("StoreFromFile(%q) did not return an expected error", tc.pathToFile)
+ }
+ if !tc.wantErr && !reflect.DeepEqual(tc.want, result.(userStore)) {
+ t.Errorf("expected StoreFromFile(%q) = %v, but got %v",
+ tc.pathToFile, tc.want, result)
+ }
+ })
+ }
+}
diff --git a/go.mod b/go.mod
index 6533530..a0f42c4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,26 +1,28 @@
-module github.com/zitadel/oidc/v3
+module git.christmann.info/LARA/zitadel-oidc/v3
-go 1.21
+go 1.23.7
+
+toolchain go1.24.1
require (
- github.com/bmatcuk/doublestar/v4 v4.6.1
- github.com/go-chi/chi/v5 v5.1.0
- github.com/go-jose/go-jose/v4 v4.0.4
+ github.com/bmatcuk/doublestar/v4 v4.8.1
+ github.com/go-chi/chi/v5 v5.2.1
+ github.com/go-jose/go-jose/v4 v4.0.5
github.com/golang/mock v1.6.0
github.com/google/go-github/v31 v31.0.0
github.com/google/uuid v1.6.0
github.com/gorilla/securecookie v1.1.2
- github.com/jeremija/gosubmit v0.2.7
+ github.com/jeremija/gosubmit v0.2.8
github.com/muhlemmer/gu v0.3.1
github.com/muhlemmer/httpforwarded v0.1.0
- github.com/rs/cors v1.11.0
+ github.com/rs/cors v1.11.1
github.com/sirupsen/logrus v1.9.3
- github.com/stretchr/testify v1.9.0
- github.com/zitadel/logging v0.6.0
- github.com/zitadel/schema v1.3.0
- go.opentelemetry.io/otel v1.28.0
- golang.org/x/oauth2 v0.22.0
- golang.org/x/text v0.17.0
+ github.com/stretchr/testify v1.10.0
+ github.com/zitadel/logging v0.6.2
+ github.com/zitadel/schema v1.3.1
+ go.opentelemetry.io/otel v1.29.0
+ golang.org/x/oauth2 v0.30.0
+ golang.org/x/text v0.26.0
)
require (
@@ -29,10 +31,10 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- go.opentelemetry.io/otel/metric v1.28.0 // indirect
- go.opentelemetry.io/otel/trace v1.28.0 // indirect
- golang.org/x/crypto v0.25.0 // indirect
- golang.org/x/net v0.26.0 // indirect
- golang.org/x/sys v0.22.0 // indirect
+ go.opentelemetry.io/otel/metric v1.29.0 // indirect
+ go.opentelemetry.io/otel/trace v1.29.0 // indirect
+ golang.org/x/crypto v0.36.0 // indirect
+ golang.org/x/net v0.38.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 19c1724..4835505 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,12 @@
-github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
-github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
+github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
-github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
-github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
-github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
+github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
+github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
+github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
+github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -29,8 +29,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
-github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
-github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
+github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA=
+github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@@ -41,40 +41,40 @@ github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
-github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
+github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/zitadel/logging v0.6.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank=
-github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow=
-github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
-github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
-go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
-go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
-go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
-go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
-go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
-go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
+github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
+github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU=
+github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU=
+go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
+go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
+go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
+go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
+go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
+go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
-golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
-golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
-golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -83,13 +83,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
-golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -101,8 +101,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/testutil/gen/gen.go b/internal/testutil/gen/gen.go
index e4a5718..3e44b7d 100644
--- a/internal/testutil/gen/gen.go
+++ b/internal/testutil/gen/gen.go
@@ -8,8 +8,8 @@ import (
"fmt"
"os"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
var custom = map[string]any{
diff --git a/internal/testutil/token.go b/internal/testutil/token.go
index 7ad8893..72d08c5 100644
--- a/internal/testutil/token.go
+++ b/internal/testutil/token.go
@@ -8,9 +8,9 @@ import (
"errors"
"time"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
jose "github.com/go-jose/go-jose/v4"
"github.com/muhlemmer/gu"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
// KeySet implements oidc.Keys
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 990da9b..2e1f536 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -15,9 +15,9 @@ import (
"go.opentelemetry.io/otel"
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/v3/pkg/crypto"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
var (
@@ -42,7 +42,7 @@ func Discover(ctx context.Context, issuer string, httpClient *http.Client, wellK
discoveryConfig := new(oidc.DiscoveryConfiguration)
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
if err != nil {
- return nil, err
+ return nil, errors.Join(oidc.ErrDiscoveryFailed, err)
}
if logger, ok := logging.FromContext(ctx); ok {
logger.Debug("discover", "config", discoveryConfig)
diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go
index e06c825..9e21e8e 100644
--- a/pkg/client/client_test.go
+++ b/pkg/client/client_test.go
@@ -5,6 +5,7 @@ import (
"net/http"
"testing"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -22,7 +23,7 @@ func TestDiscover(t *testing.T) {
name string
args args
wantFields *wantFields
- wantErr bool
+ wantErr error
}{
{
name: "spotify", // https://github.com/zitadel/oidc/issues/406
@@ -32,17 +33,20 @@ func TestDiscover(t *testing.T) {
wantFields: &wantFields{
UILocalesSupported: true,
},
- wantErr: false,
+ wantErr: nil,
+ },
+ {
+ name: "discovery failed",
+ args: args{
+ issuer: "https://example.com",
+ },
+ wantErr: oidc.ErrDiscoveryFailed,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Discover(context.Background(), tt.args.issuer, http.DefaultClient, tt.args.wellKnownUrl...)
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
- require.NoError(t, err)
+ require.ErrorIs(t, err, tt.wantErr)
if tt.wantFields == nil {
return
}
diff --git a/pkg/client/integration_test.go b/pkg/client/integration_test.go
index 98a9d3a..86a9ab7 100644
--- a/pkg/client/integration_test.go
+++ b/pkg/client/integration_test.go
@@ -23,14 +23,14 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/v3/example/server/exampleop"
- "github.com/zitadel/oidc/v3/example/server/storage"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- "github.com/zitadel/oidc/v3/pkg/client/rs"
- "github.com/zitadel/oidc/v3/pkg/client/tokenexchange"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/tokenexchange"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
var Logger = slog.New(
diff --git a/pkg/client/jwt_profile.go b/pkg/client/jwt_profile.go
index 0a5d9ec..98a54fd 100644
--- a/pkg/client/jwt_profile.go
+++ b/pkg/client/jwt_profile.go
@@ -6,8 +6,8 @@ import (
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
// JWTProfileExchange handles the oauth2 jwt profile exchange
diff --git a/pkg/client/key.go b/pkg/client/key.go
index 0c01dd2..7f38311 100644
--- a/pkg/client/key.go
+++ b/pkg/client/key.go
@@ -2,7 +2,7 @@ package client
import (
"encoding/json"
- "io/ioutil"
+ "os"
)
const (
@@ -24,7 +24,7 @@ type KeyFile struct {
}
func ConfigFromKeyFile(path string) (*KeyFile, error) {
- data, err := ioutil.ReadFile(path)
+ data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
diff --git a/pkg/client/profile/jwt_profile.go b/pkg/client/profile/jwt_profile.go
index 060f390..fb351f0 100644
--- a/pkg/client/profile/jwt_profile.go
+++ b/pkg/client/profile/jwt_profile.go
@@ -8,8 +8,8 @@ import (
jose "github.com/go-jose/go-jose/v4"
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/v3/pkg/client"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type TokenSource interface {
diff --git a/pkg/client/rp/cli/cli.go b/pkg/client/rp/cli/cli.go
index eeb9011..10edaa7 100644
--- a/pkg/client/rp/cli/cli.go
+++ b/pkg/client/rp/cli/cli.go
@@ -4,9 +4,9 @@ import (
"context"
"net/http"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
const (
diff --git a/pkg/client/rp/delegation.go b/pkg/client/rp/delegation.go
index 23ecffd..fb4fc63 100644
--- a/pkg/client/rp/delegation.go
+++ b/pkg/client/rp/delegation.go
@@ -1,7 +1,7 @@
package rp
import (
- "github.com/zitadel/oidc/v3/pkg/oidc/grants/tokenexchange"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange"
)
// DelegationTokenRequest is an implementation of TokenExchangeRequest
diff --git a/pkg/client/rp/device.go b/pkg/client/rp/device.go
index c2d1f8a..1fadd56 100644
--- a/pkg/client/rp/device.go
+++ b/pkg/client/rp/device.go
@@ -5,8 +5,8 @@ import (
"fmt"
"time"
- "github.com/zitadel/oidc/v3/pkg/client"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) {
diff --git a/pkg/client/rp/jwks.go b/pkg/client/rp/jwks.go
index c44a267..0ccbad2 100644
--- a/pkg/client/rp/jwks.go
+++ b/pkg/client/rp/jwks.go
@@ -9,9 +9,9 @@ import (
jose "github.com/go-jose/go-jose/v4"
- "github.com/zitadel/oidc/v3/pkg/client"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet {
diff --git a/pkg/client/rp/relying_party.go b/pkg/client/rp/relying_party.go
index 029a897..c2759a2 100644
--- a/pkg/client/rp/relying_party.go
+++ b/pkg/client/rp/relying_party.go
@@ -14,10 +14,10 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/zitadel/logging"
- "github.com/zitadel/oidc/v3/pkg/client"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
const (
@@ -541,7 +541,7 @@ func CodeExchangeHandler[C oidc.IDClaims](callback CodeExchangeCallback[C], rp R
rp.CookieHandler().DeleteCookie(w, pkceCode)
}
if rp.Signer() != nil {
- assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer()}, time.Hour, rp.Signer())
+ assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer(), rp.OAuthConfig().Endpoint.TokenURL}, time.Hour, rp.Signer())
if err != nil {
unauthorizedError(w, r, "failed to build assertion: "+err.Error(), state, rp)
return
diff --git a/pkg/client/rp/relying_party_test.go b/pkg/client/rp/relying_party_test.go
index 4c5a1b3..b3bb6ee 100644
--- a/pkg/client/rp/relying_party_test.go
+++ b/pkg/client/rp/relying_party_test.go
@@ -5,10 +5,10 @@ import (
"testing"
"time"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
"golang.org/x/oauth2"
)
diff --git a/pkg/client/rp/tockenexchange.go b/pkg/client/rp/tockenexchange.go
index c8ca048..aa2cf99 100644
--- a/pkg/client/rp/tockenexchange.go
+++ b/pkg/client/rp/tockenexchange.go
@@ -5,7 +5,7 @@ import (
"golang.org/x/oauth2"
- "github.com/zitadel/oidc/v3/pkg/oidc/grants/tokenexchange"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange"
)
// TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`
diff --git a/pkg/client/rp/userinfo_example_test.go b/pkg/client/rp/userinfo_example_test.go
index 2cc5222..78e014e 100644
--- a/pkg/client/rp/userinfo_example_test.go
+++ b/pkg/client/rp/userinfo_example_test.go
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type UserInfo struct {
diff --git a/pkg/client/rp/verifier.go b/pkg/client/rp/verifier.go
index ca59454..0088b81 100644
--- a/pkg/client/rp/verifier.go
+++ b/pkg/client/rp/verifier.go
@@ -6,8 +6,8 @@ import (
jose "github.com/go-jose/go-jose/v4"
- "github.com/zitadel/oidc/v3/pkg/client"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
// VerifyTokens implement the Token Response Validation as defined in OIDC specification
diff --git a/pkg/client/rp/verifier_test.go b/pkg/client/rp/verifier_test.go
index 24d35af..38f5a4a 100644
--- a/pkg/client/rp/verifier_test.go
+++ b/pkg/client/rp/verifier_test.go
@@ -5,11 +5,11 @@ import (
"testing"
"time"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
jose "github.com/go-jose/go-jose/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
func TestVerifyTokens(t *testing.T) {
diff --git a/pkg/client/rp/verifier_tokens_example_test.go b/pkg/client/rp/verifier_tokens_example_test.go
index 892eb23..7ae68d6 100644
--- a/pkg/client/rp/verifier_tokens_example_test.go
+++ b/pkg/client/rp/verifier_tokens_example_test.go
@@ -4,9 +4,9 @@ import (
"context"
"fmt"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/client/rp"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
// MyCustomClaims extends the TokenClaims base,
diff --git a/pkg/client/rs/introspect_example_test.go b/pkg/client/rs/introspect_example_test.go
index eac8be2..1f67d11 100644
--- a/pkg/client/rs/introspect_example_test.go
+++ b/pkg/client/rs/introspect_example_test.go
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
- "github.com/zitadel/oidc/v3/pkg/client/rs"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type IntrospectionResponse struct {
diff --git a/pkg/client/rs/resource_server.go b/pkg/client/rs/resource_server.go
index 962af7e..993796e 100644
--- a/pkg/client/rs/resource_server.go
+++ b/pkg/client/rs/resource_server.go
@@ -6,9 +6,9 @@ import (
"net/http"
"time"
- "github.com/zitadel/oidc/v3/pkg/client"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type ResourceServer interface {
diff --git a/pkg/client/rs/resource_server_test.go b/pkg/client/rs/resource_server_test.go
index 7a5ced9..afd7441 100644
--- a/pkg/client/rs/resource_server_test.go
+++ b/pkg/client/rs/resource_server_test.go
@@ -4,9 +4,9 @@ import (
"context"
"testing"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
func TestNewResourceServer(t *testing.T) {
diff --git a/pkg/client/tokenexchange/tokenexchange.go b/pkg/client/tokenexchange/tokenexchange.go
index 61975a4..9cc1328 100644
--- a/pkg/client/tokenexchange/tokenexchange.go
+++ b/pkg/client/tokenexchange/tokenexchange.go
@@ -6,10 +6,10 @@ import (
"net/http"
"time"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/go-jose/go-jose/v4"
- "github.com/zitadel/oidc/v3/pkg/client"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
type TokenExchanger interface {
diff --git a/pkg/crypto/key_test.go b/pkg/crypto/key_test.go
index 8ed5cb5..a6fa493 100644
--- a/pkg/crypto/key_test.go
+++ b/pkg/crypto/key_test.go
@@ -10,7 +10,7 @@ import (
"github.com/go-jose/go-jose/v4"
"github.com/stretchr/testify/assert"
- zcrypto "github.com/zitadel/oidc/v3/pkg/crypto"
+ zcrypto "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
)
func TestBytesToPrivateKey(t *testing.T) {
diff --git a/pkg/http/http.go b/pkg/http/http.go
index 33c5f15..aa0ff6f 100644
--- a/pkg/http/http.go
+++ b/pkg/http/http.go
@@ -11,7 +11,7 @@ import (
"strings"
"time"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
var DefaultHTTPClient = &http.Client{
diff --git a/pkg/oidc/code_challenge.go b/pkg/oidc/code_challenge.go
index 3296362..0c593df 100644
--- a/pkg/oidc/code_challenge.go
+++ b/pkg/oidc/code_challenge.go
@@ -3,7 +3,7 @@ package oidc
import (
"crypto/sha256"
- "github.com/zitadel/oidc/v3/pkg/crypto"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
)
const (
diff --git a/pkg/oidc/discovery.go b/pkg/oidc/discovery.go
index 14fce5e..62288d1 100644
--- a/pkg/oidc/discovery.go
+++ b/pkg/oidc/discovery.go
@@ -145,6 +145,14 @@ type DiscoveryConfiguration struct {
// OPTermsOfServiceURI is a URL the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service.
OPTermsOfServiceURI string `json:"op_tos_uri,omitempty"`
+
+ // BackChannelLogoutSupported specifies whether the OP supports back-channel logout (https://openid.net/specs/openid-connect-backchannel-1_0.html),
+ // with true indicating support. If omitted, the default value is false.
+ BackChannelLogoutSupported bool `json:"backchannel_logout_supported,omitempty"`
+
+ // BackChannelLogoutSessionSupported specifies whether the OP can pass a sid (session ID) Claim in the Logout Token to identify the RP session with the OP.
+ // If supported, the sid Claim is also included in ID Tokens issued by the OP. If omitted, the default value is false.
+ BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported,omitempty"`
}
type AuthMethod string
diff --git a/pkg/oidc/error.go b/pkg/oidc/error.go
index 1100f73..d93cf44 100644
--- a/pkg/oidc/error.go
+++ b/pkg/oidc/error.go
@@ -133,6 +133,7 @@ type Error struct {
ErrorType errorType `json:"error" schema:"error"`
Description string `json:"error_description,omitempty" schema:"error_description,omitempty"`
State string `json:"state,omitempty" schema:"state,omitempty"`
+ SessionState string `json:"session_state,omitempty" schema:"session_state,omitempty"`
redirectDisabled bool `schema:"-"`
returnParent bool `schema:"-"`
}
@@ -142,11 +143,13 @@ func (e *Error) MarshalJSON() ([]byte, error) {
Error errorType `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
State string `json:"state,omitempty"`
+ SessionState string `json:"session_state,omitempty"`
Parent string `json:"parent,omitempty"`
}{
Error: e.ErrorType,
ErrorDescription: e.Description,
State: e.State,
+ SessionState: e.SessionState,
}
if e.returnParent {
m.Parent = e.Parent.Error()
@@ -176,7 +179,8 @@ func (e *Error) Is(target error) bool {
}
return e.ErrorType == t.ErrorType &&
(e.Description == t.Description || t.Description == "") &&
- (e.State == t.State || t.State == "")
+ (e.State == t.State || t.State == "") &&
+ (e.SessionState == t.SessionState || t.SessionState == "")
}
func (e *Error) WithParent(err error) *Error {
@@ -242,6 +246,9 @@ func (e *Error) LogValue() slog.Value {
if e.State != "" {
attrs = append(attrs, slog.String("state", e.State))
}
+ if e.SessionState != "" {
+ attrs = append(attrs, slog.String("session_state", e.SessionState))
+ }
if e.redirectDisabled {
attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled))
}
diff --git a/pkg/oidc/session.go b/pkg/oidc/session.go
index b470d1e..39f9f08 100644
--- a/pkg/oidc/session.go
+++ b/pkg/oidc/session.go
@@ -1,10 +1,12 @@
package oidc
// EndSessionRequest for the RP-Initiated Logout according to:
-//https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
+// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
type EndSessionRequest struct {
- IdTokenHint string `schema:"id_token_hint"`
- ClientID string `schema:"client_id"`
- PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"`
- State string `schema:"state"`
+ IdTokenHint string `schema:"id_token_hint"`
+ LogoutHint string `schema:"logout_hint"`
+ ClientID string `schema:"client_id"`
+ PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"`
+ State string `schema:"state"`
+ UILocales Locales `schema:"ui_locales"`
}
diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go
index 5b18dac..4b43dcb 100644
--- a/pkg/oidc/token.go
+++ b/pkg/oidc/token.go
@@ -10,7 +10,7 @@ import (
"github.com/muhlemmer/gu"
- "github.com/zitadel/oidc/v3/pkg/crypto"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
)
const (
@@ -117,6 +117,7 @@ func NewAccessTokenClaims(issuer, subject string, audience []string, expiration
Expiration: FromTime(expiration),
IssuedAt: FromTime(now),
NotBefore: FromTime(now),
+ ClientID: clientID,
JWTID: jwtid,
},
}
@@ -229,12 +230,13 @@ func (c *ActorClaims) UnmarshalJSON(data []byte) error {
}
type AccessTokenResponse struct {
- AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"`
- TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"`
- RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"`
- ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"`
- IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
- State string `json:"state,omitempty" schema:"state,omitempty"`
+ AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"`
+ TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"`
+ RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"`
+ ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"`
+ IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
+ State string `json:"state,omitempty" schema:"state,omitempty"`
+ Scope SpaceDelimitedArray `json:"scope,omitempty" schema:"scope,omitempty"`
}
type JWTProfileAssertionClaims struct {
@@ -381,3 +383,40 @@ type TokenExchangeResponse struct {
// if the requested_token_type was Access Token and scope contained openid.
IDToken string `json:"id_token,omitempty"`
}
+
+type LogoutTokenClaims struct {
+ Issuer string `json:"iss,omitempty"`
+ Subject string `json:"sub,omitempty"`
+ Audience Audience `json:"aud,omitempty"`
+ IssuedAt Time `json:"iat,omitempty"`
+ Expiration Time `json:"exp,omitempty"`
+ JWTID string `json:"jti,omitempty"`
+ Events map[string]any `json:"events,omitempty"`
+ SessionID string `json:"sid,omitempty"`
+ Claims map[string]any `json:"-"`
+}
+
+type ltcAlias LogoutTokenClaims
+
+func (i *LogoutTokenClaims) MarshalJSON() ([]byte, error) {
+ return mergeAndMarshalClaims((*ltcAlias)(i), i.Claims)
+}
+
+func (i *LogoutTokenClaims) UnmarshalJSON(data []byte) error {
+ return unmarshalJSONMulti(data, (*ltcAlias)(i), &i.Claims)
+}
+
+func NewLogoutTokenClaims(issuer, subject string, audience Audience, expiration time.Time, jwtID, sessionID string, skew time.Duration) *LogoutTokenClaims {
+ return &LogoutTokenClaims{
+ Issuer: issuer,
+ Subject: subject,
+ Audience: audience,
+ IssuedAt: FromTime(time.Now().Add(-skew)),
+ Expiration: FromTime(expiration),
+ JWTID: jwtID,
+ Events: map[string]any{
+ "http://schemas.openid.net/event/backchannel-logout": struct{}{},
+ },
+ SessionID: sessionID,
+ }
+}
diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go
index f3b2ec4..dadb205 100644
--- a/pkg/oidc/token_request.go
+++ b/pkg/oidc/token_request.go
@@ -72,10 +72,10 @@ type AccessTokenRequest struct {
Code string `schema:"code"`
RedirectURI string `schema:"redirect_uri"`
ClientID string `schema:"client_id"`
- ClientSecret string `schema:"client_secret"`
- CodeVerifier string `schema:"code_verifier"`
- ClientAssertion string `schema:"client_assertion"`
- ClientAssertionType string `schema:"client_assertion_type"`
+ ClientSecret string `schema:"client_secret,omitempty"`
+ CodeVerifier string `schema:"code_verifier,omitempty"`
+ ClientAssertion string `schema:"client_assertion,omitempty"`
+ ClientAssertionType string `schema:"client_assertion_type,omitempty"`
}
func (a *AccessTokenRequest) GrantType() GrantType {
diff --git a/pkg/oidc/token_test.go b/pkg/oidc/token_test.go
index ccc3467..621cdbc 100644
--- a/pkg/oidc/token_test.go
+++ b/pkg/oidc/token_test.go
@@ -145,6 +145,7 @@ func TestNewAccessTokenClaims(t *testing.T) {
Subject: "hello@me.com",
Audience: Audience{"foo"},
Expiration: 12345,
+ ClientID: "foo",
JWTID: "900",
},
}
@@ -241,3 +242,39 @@ func TestIDTokenClaims_GetUserInfo(t *testing.T) {
got := idTokenData.GetUserInfo()
assert.Equal(t, want, got)
}
+
+func TestNewLogoutTokenClaims(t *testing.T) {
+ want := &LogoutTokenClaims{
+ Issuer: "zitadel",
+ Subject: "hello@me.com",
+ Audience: Audience{"foo", "just@me.com"},
+ Expiration: 12345,
+ JWTID: "jwtID",
+ Events: map[string]any{
+ "http://schemas.openid.net/event/backchannel-logout": struct{}{},
+ },
+ SessionID: "sessionID",
+ Claims: nil,
+ }
+
+ got := NewLogoutTokenClaims(
+ want.Issuer,
+ want.Subject,
+ want.Audience,
+ want.Expiration.AsTime(),
+ want.JWTID,
+ want.SessionID,
+ 1*time.Second,
+ )
+
+ // test if the dynamic timestamp is around now,
+ // allowing for a delta of 1, just in case we flip on
+ // either side of a second boundry.
+ nowMinusSkew := NowTime() - 1
+ assert.InDelta(t, int64(nowMinusSkew), int64(got.IssuedAt), 1)
+
+ // Make equal not fail on dynamic timestamp
+ got.IssuedAt = 0
+
+ assert.Equal(t, want, got)
+}
diff --git a/pkg/oidc/types.go b/pkg/oidc/types.go
index e7292e6..5d063b1 100644
--- a/pkg/oidc/types.go
+++ b/pkg/oidc/types.go
@@ -35,6 +35,17 @@ func (a *Audience) UnmarshalJSON(text []byte) error {
return nil
}
+func (a *Audience) MarshalJSON() ([]byte, error) {
+ len := len(*a)
+ if len > 1 {
+ return json.Marshal(*a)
+ } else if len == 1 {
+ return json.Marshal((*a)[0])
+ }
+
+ return nil, errors.New("aud is empty")
+}
+
type Display string
func (d *Display) UnmarshalText(text []byte) error {
@@ -82,6 +93,9 @@ func (l *Locale) MarshalJSON() ([]byte, error) {
// to an empty value (language "und") and no error will be returned.
// This state can be checked with the `l.Tag().IsRoot()` method.
func (l *Locale) UnmarshalJSON(data []byte) error {
+ if len(data) == 0 || string(data) == "\"\"" {
+ return nil
+ }
err := json.Unmarshal(data, &l.tag)
if err == nil {
return nil
@@ -112,6 +126,14 @@ func ParseLocales(locales []string) Locales {
return out
}
+func (l Locales) String() string {
+ tags := make([]string, len(l))
+ for i, tag := range l {
+ tags[i] = tag.String()
+ }
+ return strings.Join(tags, " ")
+}
+
// UnmarshalText implements the [encoding.TextUnmarshaler] interface.
// It decodes an unquoted space seperated string into Locales.
// Undefined language tags in the input are ignored and ommited from
@@ -228,6 +250,9 @@ func NewEncoder() *schema.Encoder {
e.RegisterEncoder(SpaceDelimitedArray{}, func(value reflect.Value) string {
return value.Interface().(SpaceDelimitedArray).String()
})
+ e.RegisterEncoder(Locales{}, func(value reflect.Value) string {
+ return value.Interface().(Locales).String()
+ })
return e
}
diff --git a/pkg/oidc/types_test.go b/pkg/oidc/types_test.go
index df93a73..53a9779 100644
--- a/pkg/oidc/types_test.go
+++ b/pkg/oidc/types_test.go
@@ -217,6 +217,30 @@ func TestLocale_UnmarshalJSON(t *testing.T) {
want dst
wantErr bool
}{
+ {
+ name: "value not present",
+ input: `{}`,
+ wantErr: false,
+ want: dst{
+ Locale: nil,
+ },
+ },
+ {
+ name: "null",
+ input: `{"locale": null}`,
+ wantErr: false,
+ want: dst{
+ Locale: nil,
+ },
+ },
+ {
+ name: "empty, ignored",
+ input: `{"locale": ""}`,
+ wantErr: false,
+ want: dst{
+ Locale: &Locale{},
+ },
+ },
{
name: "afrikaans, ok",
input: `{"locale": "af"}`,
@@ -237,16 +261,17 @@ func TestLocale_UnmarshalJSON(t *testing.T) {
wantErr: true,
},
}
-
for _, tt := range tests {
- var got dst
- err := json.Unmarshal([]byte(tt.input), &got)
- if tt.wantErr {
- require.Error(t, err)
- return
- }
- require.NoError(t, err)
- assert.Equal(t, tt.want, got)
+ t.Run(tt.name, func(t *testing.T) {
+ var got dst
+ err := json.Unmarshal([]byte(tt.input), &got)
+ if tt.wantErr {
+ require.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+ assert.Equal(t, tt.want, got)
+ })
}
}
diff --git a/pkg/oidc/verifier.go b/pkg/oidc/verifier.go
index cb66676..d5e0213 100644
--- a/pkg/oidc/verifier.go
+++ b/pkg/oidc/verifier.go
@@ -7,12 +7,11 @@ import (
"encoding/json"
"errors"
"fmt"
+ "slices"
"strings"
"time"
jose "github.com/go-jose/go-jose/v4"
-
- str "github.com/zitadel/oidc/v3/pkg/strings"
)
type Claims interface {
@@ -41,6 +40,7 @@ type IDClaims interface {
var (
ErrParse = errors.New("parsing of request failed")
ErrIssuerInvalid = errors.New("issuer does not match")
+ ErrDiscoveryFailed = errors.New("OpenID Provider Configuration Discovery has failed")
ErrSubjectMissing = errors.New("subject missing")
ErrAudience = errors.New("audience is not valid")
ErrAzpMissing = errors.New("authorized party is not set. If Token is valid for multiple audiences, azp must not be empty")
@@ -83,7 +83,7 @@ type ACRVerifier func(string) error
// if none of the provided values matches the acr claim
func DefaultACRVerifier(possibleValues []string) ACRVerifier {
return func(acr string) error {
- if !str.Contains(possibleValues, acr) {
+ if !slices.Contains(possibleValues, acr) {
return fmt.Errorf("expected one of: %v, got: %q", possibleValues, acr)
}
return nil
@@ -122,7 +122,7 @@ func CheckIssuer(claims Claims, issuer string) error {
}
func CheckAudience(claims Claims, clientID string) error {
- if !str.Contains(claims.GetAudience(), clientID) {
+ if !slices.Contains(claims.GetAudience(), clientID) {
return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, clientID)
}
diff --git a/pkg/oidc/verifier_parse_test.go b/pkg/oidc/verifier_parse_test.go
index 105650f..9cf5c1e 100644
--- a/pkg/oidc/verifier_parse_test.go
+++ b/pkg/oidc/verifier_parse_test.go
@@ -5,10 +5,10 @@ import (
"encoding/json"
"testing"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
func TestParseToken(t *testing.T) {
diff --git a/pkg/op/auth_request.go b/pkg/op/auth_request.go
index fe73180..b1434cc 100644
--- a/pkg/op/auth_request.go
+++ b/pkg/op/auth_request.go
@@ -15,10 +15,9 @@ import (
"strings"
"time"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/bmatcuk/doublestar/v4"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- str "github.com/zitadel/oidc/v3/pkg/strings"
)
type AuthRequest interface {
@@ -39,6 +38,13 @@ type AuthRequest interface {
Done() bool
}
+// AuthRequestSessionState should be implemented if [OpenID Connect Session Management](https://openid.net/specs/openid-connect-session-1_0.html) is supported
+type AuthRequestSessionState interface {
+ // GetSessionState returns session_state.
+ // session_state is related to OpenID Connect Session Management.
+ GetSessionState() string
+}
+
type Authorizer interface {
Storage() Storage
Decoder() httphelper.Decoder
@@ -56,6 +62,12 @@ type AuthorizeValidator interface {
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *IDTokenHintVerifier) (string, error)
}
+type CodeResponseType struct {
+ Code string `schema:"code"`
+ State string `schema:"state,omitempty"`
+ SessionState string `schema:"session_state,omitempty"`
+}
+
func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
Authorize(w, r, authorizer)
@@ -83,21 +95,29 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
if authReq.RequestParam != "" && authorizer.RequestObjectSupported() {
err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx))
if err != nil {
- AuthRequestError(w, r, authReq, err, authorizer)
+ AuthRequestError(w, r, nil, err, authorizer)
return
}
}
if authReq.ClientID == "" {
- AuthRequestError(w, r, authReq, fmt.Errorf("auth request is missing client_id"), authorizer)
+ AuthRequestError(w, r, nil, fmt.Errorf("auth request is missing client_id"), authorizer)
return
}
if authReq.RedirectURI == "" {
- AuthRequestError(w, r, authReq, fmt.Errorf("auth request is missing redirect_uri"), authorizer)
+ AuthRequestError(w, r, nil, fmt.Errorf("auth request is missing redirect_uri"), authorizer)
return
}
- validation := ValidateAuthRequest
- if validater, ok := authorizer.(AuthorizeValidator); ok {
- validation = validater.ValidateAuthRequest
+
+ var client Client
+ validation := func(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
+ client, err = authorizer.Storage().GetClientByClientID(ctx, authReq.ClientID)
+ if err != nil {
+ return "", oidc.ErrInvalidRequestRedirectURI().WithDescription("unable to retrieve client by id").WithParent(err)
+ }
+ return ValidateAuthRequestClient(ctx, authReq, client, verifier)
+ }
+ if validator, ok := authorizer.(AuthorizeValidator); ok {
+ validation = validator.ValidateAuthRequest
}
userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx))
if err != nil {
@@ -113,11 +133,6 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer)
return
}
- client, err := authorizer.Storage().GetClientByClientID(ctx, req.GetClientID())
- if err != nil {
- AuthRequestError(w, r, req, oidc.DefaultToServerError(err, "unable to retrieve client by id"), authorizer)
- return
- }
RedirectToLogin(req.GetID(), client, w, r)
}
@@ -153,7 +168,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage
if requestObject.Issuer != requestObject.ClientID {
return oidc.ErrInvalidRequest().WithDescription("missing or wrong issuer in request")
}
- if !str.Contains(requestObject.Audience, issuer) {
+ if !slices.Contains(requestObject.Audience, issuer) {
return oidc.ErrInvalidRequest().WithDescription("issuer missing in audience")
}
keySet := &jwtProfileKeySet{storage: storage, clientID: requestObject.Issuer}
@@ -167,7 +182,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage
// CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request
// and clears the `RequestParam` of the auth request
func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oidc.RequestObject) {
- if str.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 {
+ if slices.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 {
authReq.Scopes = requestObject.Scopes
}
if requestObject.RedirectURI != "" {
@@ -212,26 +227,37 @@ func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oi
authReq.RequestParam = ""
}
-// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed
+// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed.
+//
+// Deprecated: Use [ValidateAuthRequestClient] to prevent querying for the Client twice.
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
ctx, span := tracer.Start(ctx, "ValidateAuthRequest")
defer span.End()
+ client, err := storage.GetClientByClientID(ctx, authReq.ClientID)
+ if err != nil {
+ return "", oidc.ErrInvalidRequestRedirectURI().WithDescription("unable to retrieve client by id").WithParent(err)
+ }
+ return ValidateAuthRequestClient(ctx, authReq, client, verifier)
+}
+
+// ValidateAuthRequestClient validates the Auth request against the passed client.
+// If id_token_hint is part of the request, the subject of the token is returned.
+func ValidateAuthRequestClient(ctx context.Context, authReq *oidc.AuthRequest, client Client, verifier *IDTokenHintVerifier) (sub string, err error) {
+ ctx, span := tracer.Start(ctx, "ValidateAuthRequestClient")
+ defer span.End()
+
+ if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil {
+ return "", err
+ }
authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)
if err != nil {
return "", err
}
- client, err := storage.GetClientByClientID(ctx, authReq.ClientID)
- if err != nil {
- return "", oidc.DefaultToServerError(err, "unable to retrieve client by id")
- }
authReq.Scopes, err = ValidateAuthReqScopes(client, authReq.Scopes)
if err != nil {
return "", err
}
- if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil {
- return "", err
- }
if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil {
return "", err
}
@@ -274,7 +300,7 @@ func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) {
// checkURIAgainstRedirects just checks aginst the valid redirect URIs and ignores
// other factors.
func checkURIAgainstRedirects(client Client, uri string) error {
- if str.Contains(client.RedirectURIs(), uri) {
+ if slices.Contains(client.RedirectURIs(), uri) {
return nil
}
if globClient, ok := client.(HasRedirectGlobs); ok {
@@ -299,12 +325,12 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res
return oidc.ErrInvalidRequestRedirectURI().WithDescription("The redirect_uri is missing in the request. " +
"Please ensure it is added to the request. If you have any questions, you may contact the administrator of the application.")
}
- if strings.HasPrefix(uri, "https://") {
- return checkURIAgainstRedirects(client, uri)
- }
if client.ApplicationType() == ApplicationTypeNative {
return validateAuthReqRedirectURINative(client, uri)
}
+ if strings.HasPrefix(uri, "https://") {
+ return checkURIAgainstRedirects(client, uri)
+ }
if err := checkURIAgainstRedirects(client, uri); err != nil {
return err
}
@@ -325,12 +351,15 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res
// ValidateAuthReqRedirectURINative validates the passed redirect_uri and response_type to the registered uris and client type
func validateAuthReqRedirectURINative(client Client, uri string) error {
parsedURL, isLoopback := HTTPLoopbackOrLocalhost(uri)
- isCustomSchema := !strings.HasPrefix(uri, "http://")
+ isCustomSchema := !(strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://"))
if err := checkURIAgainstRedirects(client, uri); err == nil {
if client.DevMode() {
return nil
}
- // The RedirectURIs are only valid for native clients when localhost or non-"http://"
+ if !isLoopback && strings.HasPrefix(uri, "https://") {
+ return nil
+ }
+ // The RedirectURIs are only valid for native clients when localhost or non-"http://" and "https://"
if isLoopback || isCustomSchema {
return nil
}
@@ -360,11 +389,11 @@ func HTTPLoopbackOrLocalhost(rawURL string) (*url.URL, bool) {
if err != nil {
return nil, false
}
- if parsedURL.Scheme != "http" {
- return nil, false
+ if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" {
+ hostName := parsedURL.Hostname()
+ return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback()
}
- hostName := parsedURL.Hostname()
- return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback()
+ return nil, false
}
// ValidateAuthReqResponseType validates the passed response_type to the registered response types
@@ -454,41 +483,70 @@ func AuthResponse(authReq AuthRequest, authorizer Authorizer, w http.ResponseWri
AuthResponseToken(w, r, authReq, authorizer, client)
}
-// AuthResponseCode creates the successful code authentication response
+// AuthResponseCode handles the creation of a successful authentication response using an authorization code
func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) {
ctx, span := tracer.Start(r.Context(), "AuthResponseCode")
- r = r.WithContext(ctx)
defer span.End()
+ r = r.WithContext(ctx)
- code, err := CreateAuthRequestCode(r.Context(), authReq, authorizer.Storage(), authorizer.Crypto())
- if err != nil {
- AuthRequestError(w, r, authReq, err, authorizer)
- return
- }
- codeResponse := struct {
- Code string `schema:"code"`
- State string `schema:"state,omitempty"`
- }{
- Code: code,
- State: authReq.GetState(),
- }
-
+ var err error
if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
- err := AuthResponseFormPost(w, authReq.GetRedirectURI(), &codeResponse, authorizer.Encoder())
- if err != nil {
- AuthRequestError(w, r, authReq, err, authorizer)
- return
- }
-
- return
+ err = handleFormPostResponse(w, r, authReq, authorizer)
+ } else {
+ err = handleRedirectResponse(w, r, authReq, authorizer)
}
- callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), &codeResponse, authorizer.Encoder())
if err != nil {
AuthRequestError(w, r, authReq, err, authorizer)
- return
}
- http.Redirect(w, r, callback, http.StatusFound)
+}
+
+// handleFormPostResponse processes the authentication response using form post method
+func handleFormPostResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error {
+ codeResponse, err := BuildAuthResponseCodeResponsePayload(r.Context(), authReq, authorizer)
+ if err != nil {
+ return err
+ }
+ return AuthResponseFormPost(w, authReq.GetRedirectURI(), codeResponse, authorizer.Encoder())
+}
+
+// handleRedirectResponse processes the authentication response using the redirect method
+func handleRedirectResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error {
+ callbackURL, err := BuildAuthResponseCallbackURL(r.Context(), authReq, authorizer)
+ if err != nil {
+ return err
+ }
+ http.Redirect(w, r, callbackURL, http.StatusFound)
+ return nil
+}
+
+// BuildAuthResponseCodeResponsePayload generates the authorization code response payload for the authentication request
+func BuildAuthResponseCodeResponsePayload(ctx context.Context, authReq AuthRequest, authorizer Authorizer) (*CodeResponseType, error) {
+ code, err := CreateAuthRequestCode(ctx, authReq, authorizer.Storage(), authorizer.Crypto())
+ if err != nil {
+ return nil, err
+ }
+
+ sessionState := ""
+ if authRequestSessionState, ok := authReq.(AuthRequestSessionState); ok {
+ sessionState = authRequestSessionState.GetSessionState()
+ }
+
+ return &CodeResponseType{
+ Code: code,
+ State: authReq.GetState(),
+ SessionState: sessionState,
+ }, nil
+}
+
+// BuildAuthResponseCallbackURL generates the callback URL for a successful authorization code response
+func BuildAuthResponseCallbackURL(ctx context.Context, authReq AuthRequest, authorizer Authorizer) (string, error) {
+ codeResponse, err := BuildAuthResponseCodeResponsePayload(ctx, authReq, authorizer)
+ if err != nil {
+ return "", err
+ }
+
+ return AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), codeResponse, authorizer.Encoder())
}
// AuthResponseToken creates the successful token(s) authentication response
diff --git a/pkg/op/auth_request_test.go b/pkg/op/auth_request_test.go
index 6b4af17..d1ea965 100644
--- a/pkg/op/auth_request_test.go
+++ b/pkg/op/auth_request_test.go
@@ -11,15 +11,15 @@ import (
"reflect"
"testing"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/example/server/storage"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
- "github.com/zitadel/oidc/v3/pkg/op/mock"
"github.com/zitadel/schema"
)
@@ -433,6 +433,24 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
},
false,
},
+ {
+ "code flow registered https loopback v4 native ok",
+ args{
+ "https://127.0.0.1:4200/callback",
+ mock.NewClientWithConfig(t, []string{"https://127.0.0.1/callback"}, op.ApplicationTypeNative, nil, false),
+ oidc.ResponseTypeCode,
+ },
+ false,
+ },
+ {
+ "code flow registered https loopback v6 native ok",
+ args{
+ "https://[::1]:4200/callback",
+ mock.NewClientWithConfig(t, []string{"https://[::1]/callback"}, op.ApplicationTypeNative, nil, false),
+ oidc.ResponseTypeCode,
+ },
+ false,
+ },
{
"code flow unregistered http native fails",
args{
@@ -1072,6 +1090,34 @@ func TestAuthResponseCode(t *testing.T) {
wantBody: "",
},
},
+ {
+ name: "success with state and session_state",
+ args: args{
+ authReq: &storage.AuthRequestWithSessionState{
+ AuthRequest: &storage.AuthRequest{
+ ID: "id1",
+ TransferState: "state1",
+ },
+ SessionState: "session_state1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantCode: http.StatusFound,
+ wantLocationHeader: "/auth/callback/?code=id1&session_state=session_state1&state=state1",
+ wantBody: "",
+ },
+ },
{
name: "success without state", // reproduce issue #415
args: args{
@@ -1179,6 +1225,133 @@ func Test_parseAuthorizeCallbackRequest(t *testing.T) {
}
}
+func TestBuildAuthResponseCodeResponsePayload(t *testing.T) {
+ type args struct {
+ authReq op.AuthRequest
+ authorizer func(*testing.T) op.Authorizer
+ }
+ type res struct {
+ wantCode string
+ wantState string
+ wantSessionState string
+ wantErr bool
+ }
+ tests := []struct {
+ name string
+ args args
+ res res
+ }{
+ {
+ name: "create code error",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{
+ returnErr: io.ErrClosedPipe,
+ })
+ return authorizer
+ },
+ },
+ res: res{
+ wantErr: true,
+ },
+ },
+ {
+ name: "success with state",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ TransferState: "state1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ return authorizer
+ },
+ },
+ res: res{
+ wantCode: "id1",
+ wantState: "state1",
+ },
+ },
+ {
+ name: "success without state",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ TransferState: "",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ return authorizer
+ },
+ },
+ res: res{
+ wantCode: "id1",
+ wantState: "",
+ },
+ },
+ {
+ name: "success with session_state",
+ args: args{
+ authReq: &storage.AuthRequestWithSessionState{
+ AuthRequest: &storage.AuthRequest{
+ ID: "id1",
+ TransferState: "state1",
+ },
+ SessionState: "session_state1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ return authorizer
+ },
+ },
+ res: res{
+ wantCode: "id1",
+ wantState: "state1",
+ wantSessionState: "session_state1",
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := op.BuildAuthResponseCodeResponsePayload(context.Background(), tt.args.authReq, tt.args.authorizer(t))
+ if tt.res.wantErr {
+ assert.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+ assert.Equal(t, tt.res.wantCode, got.Code)
+ assert.Equal(t, tt.res.wantState, got.State)
+ assert.Equal(t, tt.res.wantSessionState, got.SessionState)
+ })
+ }
+}
+
func TestValidateAuthReqIDTokenHint(t *testing.T) {
token, _ := tu.ValidIDToken()
tests := []struct {
@@ -1209,3 +1382,231 @@ func TestValidateAuthReqIDTokenHint(t *testing.T) {
})
}
}
+
+func TestBuildAuthResponseCallbackURL(t *testing.T) {
+ type args struct {
+ authReq op.AuthRequest
+ authorizer func(*testing.T) op.Authorizer
+ }
+ type res struct {
+ wantURL string
+ wantErr bool
+ }
+ tests := []struct {
+ name string
+ args args
+ res res
+ }{
+ {
+ name: "error when generating code response",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{
+ returnErr: io.ErrClosedPipe,
+ })
+ return authorizer
+ },
+ },
+ res: res{
+ wantErr: true,
+ },
+ },
+ {
+ name: "error when generating callback URL",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ CallbackURI: "://invalid-url",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantErr: true,
+ },
+ },
+ {
+ name: "success with state",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ CallbackURI: "https://example.com/callback",
+ TransferState: "state1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantURL: "https://example.com/callback?code=id1&state=state1",
+ wantErr: false,
+ },
+ },
+ {
+ name: "success without state",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ CallbackURI: "https://example.com/callback",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantURL: "https://example.com/callback?code=id1",
+ wantErr: false,
+ },
+ },
+ {
+ name: "success with session_state",
+ args: args{
+ authReq: &storage.AuthRequestWithSessionState{
+ AuthRequest: &storage.AuthRequest{
+ ID: "id1",
+ CallbackURI: "https://example.com/callback",
+ TransferState: "state1",
+ },
+ SessionState: "session_state1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantURL: "https://example.com/callback?code=id1&session_state=session_state1&state=state1",
+ wantErr: false,
+ },
+ },
+ {
+ name: "success with existing query parameters",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ CallbackURI: "https://example.com/callback?param=value",
+ TransferState: "state1",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantURL: "https://example.com/callback?param=value&code=id1&state=state1",
+ wantErr: false,
+ },
+ },
+ {
+ name: "success with fragment response mode",
+ args: args{
+ authReq: &storage.AuthRequest{
+ ID: "id1",
+ CallbackURI: "https://example.com/callback",
+ TransferState: "state1",
+ ResponseMode: "fragment",
+ },
+ authorizer: func(t *testing.T) op.Authorizer {
+ ctrl := gomock.NewController(t)
+ storage := mock.NewMockStorage(ctrl)
+ storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1")
+
+ authorizer := mock.NewMockAuthorizer(ctrl)
+ authorizer.EXPECT().Storage().Return(storage)
+ authorizer.EXPECT().Crypto().Return(&mockCrypto{})
+ authorizer.EXPECT().Encoder().Return(schema.NewEncoder())
+ return authorizer
+ },
+ },
+ res: res{
+ wantURL: "https://example.com/callback#code=id1&state=state1",
+ wantErr: false,
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := op.BuildAuthResponseCallbackURL(context.Background(), tt.args.authReq, tt.args.authorizer(t))
+ if tt.res.wantErr {
+ assert.Error(t, err)
+ return
+ }
+ require.NoError(t, err)
+
+ if tt.res.wantURL != "" {
+ // Parse the URLs to compare components instead of direct string comparison
+ expectedURL, err := url.Parse(tt.res.wantURL)
+ require.NoError(t, err)
+ actualURL, err := url.Parse(got)
+ require.NoError(t, err)
+
+ // Compare the base parts (scheme, host, path)
+ assert.Equal(t, expectedURL.Scheme, actualURL.Scheme)
+ assert.Equal(t, expectedURL.Host, actualURL.Host)
+ assert.Equal(t, expectedURL.Path, actualURL.Path)
+
+ // Compare the fragment if any
+ assert.Equal(t, expectedURL.Fragment, actualURL.Fragment)
+
+ // For query parameters, compare them independently of order
+ expectedQuery := expectedURL.Query()
+ actualQuery := actualURL.Query()
+
+ assert.Equal(t, len(expectedQuery), len(actualQuery), "Query parameter count does not match")
+
+ for key, expectedValues := range expectedQuery {
+ actualValues, exists := actualQuery[key]
+ assert.True(t, exists, "Expected query parameter %s not found", key)
+ assert.ElementsMatch(t, expectedValues, actualValues, "Values for parameter %s don't match", key)
+ }
+ }
+ })
+ }
+}
diff --git a/pkg/op/client.go b/pkg/op/client.go
index 913944c..a4f44d3 100644
--- a/pkg/op/client.go
+++ b/pkg/op/client.go
@@ -7,8 +7,8 @@ import (
"net/url"
"time"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
//go:generate go get github.com/dmarkham/enumer
diff --git a/pkg/op/client_test.go b/pkg/op/client_test.go
index b772ba5..b416630 100644
--- a/pkg/op/client_test.go
+++ b/pkg/op/client_test.go
@@ -10,13 +10,13 @@ import (
"strings"
"testing"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
- "github.com/zitadel/oidc/v3/pkg/op/mock"
"github.com/zitadel/schema"
)
diff --git a/pkg/op/config.go b/pkg/op/config.go
index 9fec7cc..b271765 100644
--- a/pkg/op/config.go
+++ b/pkg/op/config.go
@@ -30,6 +30,7 @@ type Configuration interface {
EndSessionEndpoint() *Endpoint
KeysEndpoint() *Endpoint
DeviceAuthorizationEndpoint() *Endpoint
+ CheckSessionIframe() *Endpoint
AuthMethodPostSupported() bool
CodeMethodS256Supported() bool
@@ -49,6 +50,9 @@ type Configuration interface {
SupportedUILocales() []language.Tag
DeviceAuthorization() DeviceAuthorizationConfig
+
+ BackChannelLogoutSupported() bool
+ BackChannelLogoutSessionSupported() bool
}
type IssuerFromRequest func(r *http.Request) string
diff --git a/pkg/op/crypto.go b/pkg/op/crypto.go
index 6ab1e0a..01aaad3 100644
--- a/pkg/op/crypto.go
+++ b/pkg/op/crypto.go
@@ -1,7 +1,7 @@
package op
import (
- "github.com/zitadel/oidc/v3/pkg/crypto"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
)
type Crypto interface {
diff --git a/pkg/op/device.go b/pkg/op/device.go
index 11638b0..866cbc4 100644
--- a/pkg/op/device.go
+++ b/pkg/op/device.go
@@ -9,12 +9,12 @@ import (
"math/big"
"net/http"
"net/url"
+ "slices"
"strings"
"time"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- strs "github.com/zitadel/oidc/v3/pkg/strings"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type DeviceAuthorizationConfig struct {
@@ -91,10 +91,7 @@ func createDeviceAuthorization(ctx context.Context, req *oidc.DeviceAuthorizatio
}
config := o.DeviceAuthorization()
- deviceCode, err := NewDeviceCode(RecommendedDeviceCodeBytes)
- if err != nil {
- return nil, NewStatusError(err, http.StatusInternalServerError)
- }
+ deviceCode, _ := NewDeviceCode(RecommendedDeviceCodeBytes)
userCode, err := NewUserCode([]rune(config.UserCode.CharSet), config.UserCode.CharAmount, config.UserCode.DashInterval)
if err != nil {
return nil, NewStatusError(err, http.StatusInternalServerError)
@@ -163,11 +160,14 @@ func ParseDeviceCodeRequest(r *http.Request, o OpenIDProvider) (*oidc.DeviceAuth
// results in a 22 character base64 encoded string.
const RecommendedDeviceCodeBytes = 16
+// NewDeviceCode generates a new cryptographically secure device code as a base64 encoded string.
+// The length of the string is nBytes * 4 / 3.
+// An error is never returned.
+//
+// TODO(v4): change return type to string alone.
func NewDeviceCode(nBytes int) (string, error) {
bytes := make([]byte, nBytes)
- if _, err := rand.Read(bytes); err != nil {
- return "", fmt.Errorf("%w getting entropy for device code", err)
- }
+ rand.Read(bytes)
return base64.RawURLEncoding.EncodeToString(bytes), nil
}
@@ -276,7 +276,7 @@ func (r *DeviceAuthorizationState) GetAMR() []string {
}
func (r *DeviceAuthorizationState) GetAudience() []string {
- if !strs.Contains(r.Audience, r.ClientID) {
+ if !slices.Contains(r.Audience, r.ClientID) {
r.Audience = append(r.Audience, r.ClientID)
}
return r.Audience
@@ -344,10 +344,11 @@ func CreateDeviceTokenResponse(ctx context.Context, tokenRequest TokenRequest, c
RefreshToken: refreshToken,
TokenType: oidc.BearerToken,
ExpiresIn: uint64(validity.Seconds()),
+ Scope: tokenRequest.GetScopes(),
}
// TODO(v4): remove type assertion
- if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && strs.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) {
+ if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && slices.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) {
response.IDToken, err = CreateIDToken(ctx, IssuerFromContext(ctx), idTokenRequest, client.IDTokenLifetime(), accessToken, "", creator.Storage(), client)
if err != nil {
return nil, err
diff --git a/pkg/op/device_test.go b/pkg/op/device_test.go
index 570b943..a7b5c4e 100644
--- a/pkg/op/device_test.go
+++ b/pkg/op/device_test.go
@@ -13,12 +13,12 @@ import (
"testing"
"time"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/example/server/storage"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
)
func Test_deviceAuthorizationHandler(t *testing.T) {
@@ -145,21 +145,11 @@ func runWithRandReader(r io.Reader, f func()) {
}
func TestNewDeviceCode(t *testing.T) {
- t.Run("reader error", func(t *testing.T) {
- runWithRandReader(errReader{}, func() {
- _, err := op.NewDeviceCode(16)
- require.Error(t, err)
- })
- })
-
- t.Run("different lengths, rand reader", func(t *testing.T) {
- for i := 1; i <= 32; i++ {
- got, err := op.NewDeviceCode(i)
- require.NoError(t, err)
- assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i))
- }
- })
-
+ for i := 1; i <= 32; i++ {
+ got, err := op.NewDeviceCode(i)
+ require.NoError(t, err)
+ assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i))
+ }
}
func TestNewUserCode(t *testing.T) {
diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go
index cd08580..9b3ddb6 100644
--- a/pkg/op/discovery.go
+++ b/pkg/op/discovery.go
@@ -6,8 +6,8 @@ import (
jose "github.com/go-jose/go-jose/v4"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type DiscoverStorage interface {
@@ -45,6 +45,7 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di
EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer),
JwksURI: config.KeysEndpoint().Absolute(issuer),
DeviceAuthorizationEndpoint: config.DeviceAuthorizationEndpoint().Absolute(issuer),
+ CheckSessionIframe: config.CheckSessionIframe().Absolute(issuer),
ScopesSupported: Scopes(config),
ResponseTypesSupported: ResponseTypes(config),
GrantTypesSupported: GrantTypes(config),
@@ -61,6 +62,8 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di
CodeChallengeMethodsSupported: CodeChallengeMethods(config),
UILocalesSupported: config.SupportedUILocales(),
RequestParameterSupported: config.RequestObjectSupported(),
+ BackChannelLogoutSupported: config.BackChannelLogoutSupported(),
+ BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(),
}
}
@@ -92,11 +95,17 @@ func createDiscoveryConfigV2(ctx context.Context, config Configuration, storage
CodeChallengeMethodsSupported: CodeChallengeMethods(config),
UILocalesSupported: config.SupportedUILocales(),
RequestParameterSupported: config.RequestObjectSupported(),
+ BackChannelLogoutSupported: config.BackChannelLogoutSupported(),
+ BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(),
}
}
func Scopes(c Configuration) []string {
- return DefaultSupportedScopes // TODO: config
+ provider, ok := c.(*Provider)
+ if ok && provider.config.SupportedScopes != nil {
+ return provider.config.SupportedScopes
+ }
+ return DefaultSupportedScopes
}
func ResponseTypes(c Configuration) []string {
@@ -131,7 +140,7 @@ func GrantTypes(c Configuration) []oidc.GrantType {
}
func SubjectTypes(c Configuration) []string {
- return []string{"public"} //TODO: config
+ return []string{"public"} // TODO: config
}
func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string {
diff --git a/pkg/op/discovery_test.go b/pkg/op/discovery_test.go
index cb4cfba..63f1b98 100644
--- a/pkg/op/discovery_test.go
+++ b/pkg/op/discovery_test.go
@@ -11,9 +11,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
- "github.com/zitadel/oidc/v3/pkg/op/mock"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
)
func TestDiscover(t *testing.T) {
@@ -81,6 +81,11 @@ func Test_scopes(t *testing.T) {
args{},
op.DefaultSupportedScopes,
},
+ {
+ "custom scopes",
+ args{newTestProvider(&op.Config{SupportedScopes: []string{"test1", "test2"}})},
+ []string{"test1", "test2"},
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/op/endpoint_test.go b/pkg/op/endpoint_test.go
index bf112ef..5b98c6e 100644
--- a/pkg/op/endpoint_test.go
+++ b/pkg/op/endpoint_test.go
@@ -3,8 +3,8 @@ package op_test
import (
"testing"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/pkg/op"
)
func TestEndpoint_Path(t *testing.T) {
diff --git a/pkg/op/error.go b/pkg/op/error.go
index 44b1798..272f85e 100644
--- a/pkg/op/error.go
+++ b/pkg/op/error.go
@@ -7,8 +7,8 @@ import (
"log/slog"
"net/http"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type ErrAuthRequest interface {
@@ -46,6 +46,12 @@ func AuthRequestError(w http.ResponseWriter, r *http.Request, authReq ErrAuthReq
return
}
e.State = authReq.GetState()
+ var sessionState string
+ authRequestSessionState, ok := authReq.(AuthRequestSessionState)
+ if ok {
+ sessionState = authRequestSessionState.GetSessionState()
+ }
+ e.SessionState = sessionState
var responseMode oidc.ResponseMode
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
responseMode = rm.GetResponseMode()
@@ -92,6 +98,12 @@ func TryErrorRedirect(ctx context.Context, authReq ErrAuthRequest, parent error,
}
e.State = authReq.GetState()
+ var sessionState string
+ authRequestSessionState, ok := authReq.(AuthRequestSessionState)
+ if ok {
+ sessionState = authRequestSessionState.GetSessionState()
+ }
+ e.SessionState = sessionState
var responseMode oidc.ResponseMode
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
responseMode = rm.GetResponseMode()
diff --git a/pkg/op/error_test.go b/pkg/op/error_test.go
index 107f9d0..9271cf1 100644
--- a/pkg/op/error_test.go
+++ b/pkg/op/error_test.go
@@ -11,9 +11,9 @@ import (
"strings"
"testing"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/schema"
)
diff --git a/pkg/op/keys.go b/pkg/op/keys.go
index c96c456..97e400b 100644
--- a/pkg/op/keys.go
+++ b/pkg/op/keys.go
@@ -6,7 +6,7 @@ import (
jose "github.com/go-jose/go-jose/v4"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
)
type KeyProvider interface {
diff --git a/pkg/op/keys_test.go b/pkg/op/keys_test.go
index 3662739..9c80878 100644
--- a/pkg/op/keys_test.go
+++ b/pkg/op/keys_test.go
@@ -11,9 +11,9 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
- "github.com/zitadel/oidc/v3/pkg/op/mock"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock"
)
func TestKeys(t *testing.T) {
diff --git a/pkg/op/mock/authorizer.mock.go b/pkg/op/mock/authorizer.mock.go
index c7703f1..56b28e0 100644
--- a/pkg/op/mock/authorizer.mock.go
+++ b/pkg/op/mock/authorizer.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Authorizer)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Authorizer)
// Package mock is a generated GoMock package.
package mock
@@ -9,9 +9,9 @@ import (
slog "log/slog"
reflect "reflect"
+ http "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock"
- http "github.com/zitadel/oidc/v3/pkg/http"
- op "github.com/zitadel/oidc/v3/pkg/op"
)
// MockAuthorizer is a mock of Authorizer interface.
diff --git a/pkg/op/mock/authorizer.mock.impl.go b/pkg/op/mock/authorizer.mock.impl.go
index 59e8fa3..73c4154 100644
--- a/pkg/op/mock/authorizer.mock.impl.go
+++ b/pkg/op/mock/authorizer.mock.impl.go
@@ -8,8 +8,8 @@ import (
"github.com/golang/mock/gomock"
"github.com/zitadel/schema"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
func NewAuthorizer(t *testing.T) op.Authorizer {
diff --git a/pkg/op/mock/client.go b/pkg/op/mock/client.go
index f01e3ec..e2a5e85 100644
--- a/pkg/op/mock/client.go
+++ b/pkg/op/mock/client.go
@@ -5,8 +5,8 @@ import (
"github.com/golang/mock/gomock"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
func NewClient(t *testing.T) op.Client {
diff --git a/pkg/op/mock/client.mock.go b/pkg/op/mock/client.mock.go
index 9be0807..93eca67 100644
--- a/pkg/op/mock/client.mock.go
+++ b/pkg/op/mock/client.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Client)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Client)
// Package mock is a generated GoMock package.
package mock
@@ -8,9 +8,9 @@ import (
reflect "reflect"
time "time"
+ oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock"
- oidc "github.com/zitadel/oidc/v3/pkg/oidc"
- op "github.com/zitadel/oidc/v3/pkg/op"
)
// MockClient is a mock of Client interface.
diff --git a/pkg/op/mock/configuration.mock.go b/pkg/op/mock/configuration.mock.go
index f392a45..bf51035 100644
--- a/pkg/op/mock/configuration.mock.go
+++ b/pkg/op/mock/configuration.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Configuration)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Configuration)
// Package mock is a generated GoMock package.
package mock
@@ -8,8 +8,8 @@ import (
http "net/http"
reflect "reflect"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock"
- op "github.com/zitadel/oidc/v3/pkg/op"
language "golang.org/x/text/language"
)
@@ -78,6 +78,48 @@ func (mr *MockConfigurationMockRecorder) AuthorizationEndpoint() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint))
}
+// BackChannelLogoutSessionSupported mocks base method.
+func (m *MockConfiguration) BackChannelLogoutSessionSupported() bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "BackChannelLogoutSessionSupported")
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// BackChannelLogoutSessionSupported indicates an expected call of BackChannelLogoutSessionSupported.
+func (mr *MockConfigurationMockRecorder) BackChannelLogoutSessionSupported() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackChannelLogoutSessionSupported", reflect.TypeOf((*MockConfiguration)(nil).BackChannelLogoutSessionSupported))
+}
+
+// BackChannelLogoutSupported mocks base method.
+func (m *MockConfiguration) BackChannelLogoutSupported() bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "BackChannelLogoutSupported")
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// BackChannelLogoutSupported indicates an expected call of BackChannelLogoutSupported.
+func (mr *MockConfigurationMockRecorder) BackChannelLogoutSupported() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackChannelLogoutSupported", reflect.TypeOf((*MockConfiguration)(nil).BackChannelLogoutSupported))
+}
+
+// CheckSessionIframe mocks base method.
+func (m *MockConfiguration) CheckSessionIframe() *op.Endpoint {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CheckSessionIframe")
+ ret0, _ := ret[0].(*op.Endpoint)
+ return ret0
+}
+
+// CheckSessionIframe indicates an expected call of CheckSessionIframe.
+func (mr *MockConfigurationMockRecorder) CheckSessionIframe() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckSessionIframe", reflect.TypeOf((*MockConfiguration)(nil).CheckSessionIframe))
+}
+
// CodeMethodS256Supported mocks base method.
func (m *MockConfiguration) CodeMethodS256Supported() bool {
m.ctrl.T.Helper()
diff --git a/pkg/op/mock/discovery.mock.go b/pkg/op/mock/discovery.mock.go
index a27f8ef..c85f91b 100644
--- a/pkg/op/mock/discovery.mock.go
+++ b/pkg/op/mock/discovery.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: DiscoverStorage)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: DiscoverStorage)
// Package mock is a generated GoMock package.
package mock
diff --git a/pkg/op/mock/generate.go b/pkg/op/mock/generate.go
index e5cab3e..3d58ab7 100644
--- a/pkg/op/mock/generate.go
+++ b/pkg/op/mock/generate.go
@@ -1,11 +1,11 @@
package mock
//go:generate go install github.com/golang/mock/mockgen@v1.6.0
-//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v3/pkg/op Storage
-//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v3/pkg/op Authorizer
-//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v3/pkg/op Client
-//go:generate mockgen -package mock -destination ./glob.mock.go github.com/zitadel/oidc/v3/pkg/op HasRedirectGlobs
-//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v3/pkg/op Configuration
-//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v3/pkg/op DiscoverStorage
-//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v3/pkg/op SigningKey,Key
-//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/v3/pkg/op KeyProvider
+//go:generate mockgen -package mock -destination ./storage.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Storage
+//go:generate mockgen -package mock -destination ./authorizer.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Authorizer
+//go:generate mockgen -package mock -destination ./client.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Client
+//go:generate mockgen -package mock -destination ./glob.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op HasRedirectGlobs
+//go:generate mockgen -package mock -destination ./configuration.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Configuration
+//go:generate mockgen -package mock -destination ./discovery.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op DiscoverStorage
+//go:generate mockgen -package mock -destination ./signer.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op SigningKey,Key
+//go:generate mockgen -package mock -destination ./key.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op KeyProvider
diff --git a/pkg/op/mock/glob.go b/pkg/op/mock/glob.go
index cade476..8149c8f 100644
--- a/pkg/op/mock/glob.go
+++ b/pkg/op/mock/glob.go
@@ -3,9 +3,9 @@ package mock
import (
"testing"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- op "github.com/zitadel/oidc/v3/pkg/op"
)
func NewHasRedirectGlobs(t *testing.T) op.HasRedirectGlobs {
diff --git a/pkg/op/mock/glob.mock.go b/pkg/op/mock/glob.mock.go
index cf9996e..ebdc333 100644
--- a/pkg/op/mock/glob.mock.go
+++ b/pkg/op/mock/glob.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: HasRedirectGlobs)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: HasRedirectGlobs)
// Package mock is a generated GoMock package.
package mock
@@ -8,9 +8,9 @@ import (
reflect "reflect"
time "time"
+ oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock"
- oidc "github.com/zitadel/oidc/v3/pkg/oidc"
- op "github.com/zitadel/oidc/v3/pkg/op"
)
// MockHasRedirectGlobs is a mock of HasRedirectGlobs interface.
diff --git a/pkg/op/mock/key.mock.go b/pkg/op/mock/key.mock.go
index 122e852..d9ee857 100644
--- a/pkg/op/mock/key.mock.go
+++ b/pkg/op/mock/key.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: KeyProvider)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: KeyProvider)
// Package mock is a generated GoMock package.
package mock
@@ -8,8 +8,8 @@ import (
context "context"
reflect "reflect"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock"
- op "github.com/zitadel/oidc/v3/pkg/op"
)
// MockKeyProvider is a mock of KeyProvider interface.
diff --git a/pkg/op/mock/signer.mock.go b/pkg/op/mock/signer.mock.go
index e1bab91..751ce60 100644
--- a/pkg/op/mock/signer.mock.go
+++ b/pkg/op/mock/signer.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: SigningKey,Key)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: SigningKey,Key)
// Package mock is a generated GoMock package.
package mock
diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go
index 02a7c5c..0df9830 100644
--- a/pkg/op/mock/storage.mock.go
+++ b/pkg/op/mock/storage.mock.go
@@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Storage)
+// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Storage)
// Package mock is a generated GoMock package.
package mock
@@ -9,10 +9,10 @@ import (
reflect "reflect"
time "time"
+ oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
jose "github.com/go-jose/go-jose/v4"
gomock "github.com/golang/mock/gomock"
- oidc "github.com/zitadel/oidc/v3/pkg/oidc"
- op "github.com/zitadel/oidc/v3/pkg/op"
)
// MockStorage is a mock of Storage interface.
diff --git a/pkg/op/mock/storage.mock.impl.go b/pkg/op/mock/storage.mock.impl.go
index 002da7e..96e08a9 100644
--- a/pkg/op/mock/storage.mock.impl.go
+++ b/pkg/op/mock/storage.mock.impl.go
@@ -8,8 +8,8 @@ import (
"github.com/golang/mock/gomock"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
func NewStorage(t *testing.T) op.Storage {
diff --git a/pkg/op/op.go b/pkg/op/op.go
index 61c2449..76c2c89 100644
--- a/pkg/op/op.go
+++ b/pkg/op/op.go
@@ -14,8 +14,8 @@ import (
"go.opentelemetry.io/otel"
"golang.org/x/text/language"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
const (
@@ -158,16 +158,19 @@ func authCallbackPath(o OpenIDProvider) string {
}
type Config struct {
- CryptoKey [32]byte
- DefaultLogoutRedirectURI string
- CodeMethodS256 bool
- AuthMethodPost bool
- AuthMethodPrivateKeyJWT bool
- GrantTypeRefreshToken bool
- RequestObjectSupported bool
- SupportedUILocales []language.Tag
- SupportedClaims []string
- DeviceAuthorization DeviceAuthorizationConfig
+ CryptoKey [32]byte
+ DefaultLogoutRedirectURI string
+ CodeMethodS256 bool
+ AuthMethodPost bool
+ AuthMethodPrivateKeyJWT bool
+ GrantTypeRefreshToken bool
+ RequestObjectSupported bool
+ SupportedUILocales []language.Tag
+ SupportedClaims []string
+ SupportedScopes []string
+ DeviceAuthorization DeviceAuthorizationConfig
+ BackChannelLogoutSupported bool
+ BackChannelLogoutSessionSupported bool
}
// Endpoints defines endpoint routes.
@@ -336,6 +339,10 @@ func (o *Provider) DeviceAuthorizationEndpoint() *Endpoint {
return o.endpoints.DeviceAuthorization
}
+func (o *Provider) CheckSessionIframe() *Endpoint {
+ return o.endpoints.CheckSessionIframe
+}
+
func (o *Provider) KeysEndpoint() *Endpoint {
return o.endpoints.JwksURI
}
@@ -411,6 +418,14 @@ func (o *Provider) DeviceAuthorization() DeviceAuthorizationConfig {
return o.config.DeviceAuthorization
}
+func (o *Provider) BackChannelLogoutSupported() bool {
+ return o.config.BackChannelLogoutSupported
+}
+
+func (o *Provider) BackChannelLogoutSessionSupported() bool {
+ return o.config.BackChannelLogoutSessionSupported
+}
+
func (o *Provider) Storage() Storage {
return o.storage
}
diff --git a/pkg/op/op_test.go b/pkg/op/op_test.go
index 83032d4..e1ac0bd 100644
--- a/pkg/op/op_test.go
+++ b/pkg/op/op_test.go
@@ -11,12 +11,12 @@ import (
"testing"
"time"
+ "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/example/server/storage"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
"golang.org/x/text/language"
)
@@ -232,7 +232,7 @@ func TestRoutes(t *testing.T) {
"scope": oidc.SpaceDelimitedArray{oidc.ScopeOpenID, oidc.ScopeOfflineAccess}.String(),
},
wantCode: http.StatusOK,
- contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299}`},
+ contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299,"scope":"openid offline_access"}`},
},
{
// This call will fail. A successful test is already
diff --git a/pkg/op/probes.go b/pkg/op/probes.go
index cb3853d..fa713da 100644
--- a/pkg/op/probes.go
+++ b/pkg/op/probes.go
@@ -5,7 +5,7 @@ import (
"errors"
"net/http"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
)
type ProbesFn func(context.Context) error
diff --git a/pkg/op/server.go b/pkg/op/server.go
index b500e43..d45b734 100644
--- a/pkg/op/server.go
+++ b/pkg/op/server.go
@@ -5,9 +5,9 @@ import (
"net/http"
"net/url"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/muhlemmer/gu"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
// Server describes the interface that needs to be implemented to serve
diff --git a/pkg/op/server_http.go b/pkg/op/server_http.go
index 725dd64..d71a354 100644
--- a/pkg/op/server_http.go
+++ b/pkg/op/server_http.go
@@ -6,11 +6,11 @@ import (
"net/http"
"net/url"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/go-chi/chi/v5"
"github.com/rs/cors"
"github.com/zitadel/logging"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/schema"
)
diff --git a/pkg/op/server_http_routes_test.go b/pkg/op/server_http_routes_test.go
index 2c83ad3..02200ee 100644
--- a/pkg/op/server_http_routes_test.go
+++ b/pkg/op/server_http_routes_test.go
@@ -14,9 +14,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/zitadel/oidc/v3/pkg/client"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
func jwtProfile() (string, error) {
@@ -145,7 +145,7 @@ func TestServerRoutes(t *testing.T) {
"assertion": jwtProfileToken,
},
wantCode: http.StatusOK,
- contains: []string{`{"access_token":`, `"token_type":"Bearer","expires_in":299}`},
+ contains: []string{`{"access_token":`, `"token_type":"Bearer","expires_in":299,"scope":"openid"}`},
},
{
name: "Token exchange",
@@ -174,7 +174,7 @@ func TestServerRoutes(t *testing.T) {
"scope": oidc.SpaceDelimitedArray{oidc.ScopeOpenID, oidc.ScopeOfflineAccess}.String(),
},
wantCode: http.StatusOK,
- contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299}`},
+ contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299,"scope":"openid offline_access"}`},
},
{
// This call will fail. A successful test is already
diff --git a/pkg/op/server_http_test.go b/pkg/op/server_http_test.go
index 9ff07bc..75d02ca 100644
--- a/pkg/op/server_http_test.go
+++ b/pkg/op/server_http_test.go
@@ -14,11 +14,11 @@ import (
"testing"
"time"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/schema"
)
diff --git a/pkg/op/server_legacy.go b/pkg/op/server_legacy.go
index 126fde1..06e4e93 100644
--- a/pkg/op/server_legacy.go
+++ b/pkg/op/server_legacy.go
@@ -6,8 +6,8 @@ import (
"net/http"
"time"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/go-chi/chi/v5"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
// ExtendedLegacyServer allows embedding [LegacyServer] in a struct,
diff --git a/pkg/op/session.go b/pkg/op/session.go
index 8ac530d..ac663c9 100644
--- a/pkg/op/session.go
+++ b/pkg/op/session.go
@@ -8,8 +8,8 @@ import (
"net/url"
"path"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type SessionEnder interface {
@@ -73,6 +73,8 @@ func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest,
session := &EndSessionRequest{
RedirectURI: ender.DefaultLogoutRedirectURI(),
+ LogoutHint: req.LogoutHint,
+ UILocales: req.UILocales,
}
if req.IdTokenHint != "" {
claims, err := VerifyIDTokenHint[*oidc.IDTokenClaims](ctx, req.IdTokenHint, ender.IDTokenHintVerifier(ctx))
diff --git a/pkg/op/storage.go b/pkg/op/storage.go
index 8488b28..2dbd124 100644
--- a/pkg/op/storage.go
+++ b/pkg/op/storage.go
@@ -6,8 +6,9 @@ import (
"time"
jose "github.com/go-jose/go-jose/v4"
+ "golang.org/x/text/language"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type AuthStorage interface {
@@ -144,6 +145,12 @@ type CanSetUserinfoFromRequest interface {
SetUserinfoFromRequest(ctx context.Context, userinfo *oidc.UserInfo, request IDTokenRequest, scopes []string) error
}
+// CanGetPrivateClaimsFromRequest is an optional additional interface that may be implemented by
+// implementors of Storage. It allows setting the jwt token claims based on the request.
+type CanGetPrivateClaimsFromRequest interface {
+ GetPrivateClaimsFromRequest(ctx context.Context, request TokenRequest, restrictedScopes []string) (map[string]any, error)
+}
+
// Storage is a required parameter for NewOpenIDProvider(). In addition to the
// embedded interfaces below, if the passed Storage implements ClientCredentialsStorage
// then the grant type "client_credentials" will be supported. In that case, the access
@@ -164,6 +171,8 @@ type EndSessionRequest struct {
ClientID string
IDTokenHintClaims *oidc.IDTokenClaims
RedirectURI string
+ LogoutHint string
+ UILocales []language.Tag
}
var ErrDuplicateUserCode = errors.New("user code already exists")
diff --git a/pkg/op/token.go b/pkg/op/token.go
index b45789b..2e25d05 100644
--- a/pkg/op/token.go
+++ b/pkg/op/token.go
@@ -2,11 +2,11 @@ package op
import (
"context"
+ "slices"
"time"
- "github.com/zitadel/oidc/v3/pkg/crypto"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/strings"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type TokenCreator interface {
@@ -65,6 +65,7 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli
TokenType: oidc.BearerToken,
ExpiresIn: exp,
State: state,
+ Scope: request.GetScopes(),
}, nil
}
@@ -82,13 +83,13 @@ func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storag
func needsRefreshToken(tokenRequest TokenRequest, client AccessTokenClient) bool {
switch req := tokenRequest.(type) {
case AuthRequest:
- return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken)
+ return slices.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken)
case TokenExchangeRequest:
return req.GetRequestedTokenType() == oidc.RefreshTokenType
case RefreshTokenRequest:
return true
case *DeviceAuthorizationState:
- return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && ValidateGrantType(client, oidc.GrantTypeRefreshToken)
+ return slices.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && ValidateGrantType(client, oidc.GrantTypeRefreshToken)
default:
return false
}
@@ -146,7 +147,11 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex
tokenExchangeRequest,
)
} else {
- privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes))
+ if fromRequest, ok := storage.(CanGetPrivateClaimsFromRequest); ok {
+ privateClaims, err = fromRequest.GetPrivateClaimsFromRequest(ctx, tokenRequest, removeUserinfoScopes(restrictedScopes))
+ } else {
+ privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes))
+ }
}
if err != nil {
diff --git a/pkg/op/token_client_credentials.go b/pkg/op/token_client_credentials.go
index 7f1debe..ddb2fbf 100644
--- a/pkg/op/token_client_credentials.go
+++ b/pkg/op/token_client_credentials.go
@@ -5,8 +5,8 @@ import (
"net/http"
"net/url"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
// ClientCredentialsExchange handles the OAuth 2.0 client_credentials grant, including
@@ -120,5 +120,6 @@ func CreateClientCredentialsTokenResponse(ctx context.Context, tokenRequest Toke
AccessToken: accessToken,
TokenType: oidc.BearerToken,
ExpiresIn: uint64(validity.Seconds()),
+ Scope: tokenRequest.GetScopes(),
}, nil
}
diff --git a/pkg/op/token_code.go b/pkg/op/token_code.go
index 3612240..155aa43 100644
--- a/pkg/op/token_code.go
+++ b/pkg/op/token_code.go
@@ -4,8 +4,8 @@ import (
"context"
"net/http"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
// CodeExchange handles the OAuth 2.0 authorization_code grant, including
diff --git a/pkg/op/token_exchange.go b/pkg/op/token_exchange.go
index fcb4468..00af485 100644
--- a/pkg/op/token_exchange.go
+++ b/pkg/op/token_exchange.go
@@ -7,8 +7,8 @@ import (
"strings"
"time"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type TokenExchangeRequest interface {
diff --git a/pkg/op/token_intospection.go b/pkg/op/token_intospection.go
index 29234e1..bb6a5a0 100644
--- a/pkg/op/token_intospection.go
+++ b/pkg/op/token_intospection.go
@@ -5,8 +5,8 @@ import (
"errors"
"net/http"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type Introspector interface {
diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go
index 96ce1ed..defb937 100644
--- a/pkg/op/token_jwt_profile.go
+++ b/pkg/op/token_jwt_profile.go
@@ -5,8 +5,8 @@ import (
"net/http"
"time"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type JWTAuthorizationGrantExchanger interface {
@@ -89,6 +89,7 @@ func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, crea
AccessToken: accessToken,
TokenType: oidc.BearerToken,
ExpiresIn: uint64(validity.Seconds()),
+ Scope: tokenRequest.GetScopes(),
}, nil
}
diff --git a/pkg/op/token_refresh.go b/pkg/op/token_refresh.go
index 92ef476..a87e883 100644
--- a/pkg/op/token_refresh.go
+++ b/pkg/op/token_refresh.go
@@ -4,11 +4,11 @@ import (
"context"
"errors"
"net/http"
+ "slices"
"time"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/strings"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type RefreshTokenRequest interface {
@@ -85,7 +85,7 @@ func ValidateRefreshTokenScopes(requestedScopes []string, authRequest RefreshTok
return nil
}
for _, scope := range requestedScopes {
- if !strings.Contains(authRequest.GetScopes(), scope) {
+ if !slices.Contains(authRequest.GetScopes(), scope) {
return oidc.ErrInvalidScope()
}
}
diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go
index 85e2270..3f5af7a 100644
--- a/pkg/op/token_request.go
+++ b/pkg/op/token_request.go
@@ -6,8 +6,8 @@ import (
"net/http"
"net/url"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type Exchanger interface {
@@ -132,11 +132,19 @@ func AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string,
// AuthorizeCodeChallenge authorizes a client by validating the code_verifier against the previously sent
// code_challenge of the auth request (PKCE)
func AuthorizeCodeChallenge(codeVerifier string, challenge *oidc.CodeChallenge) error {
+ if challenge == nil {
+ if codeVerifier != "" {
+ return oidc.ErrInvalidRequest().WithDescription("code_verifier unexpectedly provided")
+ }
+
+ return nil
+ }
+
if codeVerifier == "" {
- return oidc.ErrInvalidRequest().WithDescription("code_challenge required")
+ return oidc.ErrInvalidRequest().WithDescription("code_verifier required")
}
if !oidc.VerifyCodeChallenge(challenge, codeVerifier) {
- return oidc.ErrInvalidGrant().WithDescription("invalid code challenge")
+ return oidc.ErrInvalidGrant().WithDescription("invalid code_verifier")
}
return nil
}
diff --git a/pkg/op/token_request_test.go b/pkg/op/token_request_test.go
new file mode 100644
index 0000000..d226af6
--- /dev/null
+++ b/pkg/op/token_request_test.go
@@ -0,0 +1,75 @@
+package op_test
+
+import (
+ "testing"
+
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestAuthorizeCodeChallenge(t *testing.T) {
+ tests := []struct {
+ name string
+ codeVerifier string
+ codeChallenge *oidc.CodeChallenge
+ want func(t *testing.T, err error)
+ }{
+ {
+ name: "missing both code_verifier and code_challenge",
+ codeVerifier: "",
+ codeChallenge: nil,
+ want: func(t *testing.T, err error) {
+ assert.Nil(t, err)
+ },
+ },
+ {
+ name: "valid code_verifier",
+ codeVerifier: "Hello World!",
+ codeChallenge: &oidc.CodeChallenge{
+ Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk",
+ Method: oidc.CodeChallengeMethodS256,
+ },
+ want: func(t *testing.T, err error) {
+ assert.Nil(t, err)
+ },
+ },
+ {
+ name: "invalid code_verifier",
+ codeVerifier: "Hi World!",
+ codeChallenge: &oidc.CodeChallenge{
+ Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk",
+ Method: oidc.CodeChallengeMethodS256,
+ },
+ want: func(t *testing.T, err error) {
+ assert.ErrorContains(t, err, "invalid code_verifier")
+ },
+ },
+ {
+ name: "code_verifier provided without code_challenge",
+ codeVerifier: "code_verifier",
+ codeChallenge: nil,
+ want: func(t *testing.T, err error) {
+ assert.ErrorContains(t, err, "code_verifier unexpectedly provided")
+ },
+ },
+ {
+ name: "empty code_verifier",
+ codeVerifier: "",
+ codeChallenge: &oidc.CodeChallenge{
+ Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk",
+ Method: oidc.CodeChallengeMethodS256,
+ },
+ want: func(t *testing.T, err error) {
+ assert.ErrorContains(t, err, "code_verifier required")
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := op.AuthorizeCodeChallenge(tt.codeVerifier, tt.codeChallenge)
+
+ tt.want(t, err)
+ })
+ }
+}
diff --git a/pkg/op/token_revocation.go b/pkg/op/token_revocation.go
index a86a481..049ee15 100644
--- a/pkg/op/token_revocation.go
+++ b/pkg/op/token_revocation.go
@@ -7,8 +7,8 @@ import (
"net/url"
"strings"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type Revoker interface {
diff --git a/pkg/op/userinfo.go b/pkg/op/userinfo.go
index 839b139..ff75e72 100644
--- a/pkg/op/userinfo.go
+++ b/pkg/op/userinfo.go
@@ -6,8 +6,8 @@ import (
"net/http"
"strings"
- httphelper "github.com/zitadel/oidc/v3/pkg/http"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type UserinfoProvider interface {
diff --git a/pkg/op/verifier_access_token.go b/pkg/op/verifier_access_token.go
index 6ac29f2..585ca54 100644
--- a/pkg/op/verifier_access_token.go
+++ b/pkg/op/verifier_access_token.go
@@ -3,7 +3,7 @@ package op
import (
"context"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type AccessTokenVerifier oidc.Verifier
diff --git a/pkg/op/verifier_access_token_example_test.go b/pkg/op/verifier_access_token_example_test.go
index 397a2d3..b97a7fd 100644
--- a/pkg/op/verifier_access_token_example_test.go
+++ b/pkg/op/verifier_access_token_example_test.go
@@ -4,9 +4,9 @@ import (
"context"
"fmt"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
)
// MyCustomClaims extends the TokenClaims base,
diff --git a/pkg/op/verifier_access_token_test.go b/pkg/op/verifier_access_token_test.go
index 66e32ce..5845f9f 100644
--- a/pkg/op/verifier_access_token_test.go
+++ b/pkg/op/verifier_access_token_test.go
@@ -5,10 +5,10 @@ import (
"testing"
"time"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
func TestNewAccessTokenVerifier(t *testing.T) {
diff --git a/pkg/op/verifier_id_token_hint.go b/pkg/op/verifier_id_token_hint.go
index 331c64c..02610aa 100644
--- a/pkg/op/verifier_id_token_hint.go
+++ b/pkg/op/verifier_id_token_hint.go
@@ -4,7 +4,7 @@ import (
"context"
"errors"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type IDTokenHintVerifier oidc.Verifier
diff --git a/pkg/op/verifier_id_token_hint_test.go b/pkg/op/verifier_id_token_hint_test.go
index 597e291..347e33c 100644
--- a/pkg/op/verifier_id_token_hint_test.go
+++ b/pkg/op/verifier_id_token_hint_test.go
@@ -6,10 +6,10 @@ import (
"testing"
"time"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
)
func TestNewIDTokenHintVerifier(t *testing.T) {
diff --git a/pkg/op/verifier_jwt_profile.go b/pkg/op/verifier_jwt_profile.go
index 06a7d34..85bfb14 100644
--- a/pkg/op/verifier_jwt_profile.go
+++ b/pkg/op/verifier_jwt_profile.go
@@ -8,7 +8,7 @@ import (
jose "github.com/go-jose/go-jose/v4"
- "github.com/zitadel/oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
// JWTProfileVerfiier extends oidc.Verifier with
diff --git a/pkg/op/verifier_jwt_profile_test.go b/pkg/op/verifier_jwt_profile_test.go
index d96cbb4..2068678 100644
--- a/pkg/op/verifier_jwt_profile_test.go
+++ b/pkg/op/verifier_jwt_profile_test.go
@@ -5,11 +5,11 @@ import (
"testing"
"time"
+ tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
+ "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- tu "github.com/zitadel/oidc/v3/internal/testutil"
- "github.com/zitadel/oidc/v3/pkg/oidc"
- "github.com/zitadel/oidc/v3/pkg/op"
)
func TestNewJWTProfileVerifier(t *testing.T) {
diff --git a/pkg/strings/strings.go b/pkg/strings/strings.go
index af48cf3..b8f43a1 100644
--- a/pkg/strings/strings.go
+++ b/pkg/strings/strings.go
@@ -1,10 +1,9 @@
package strings
+import "slices"
+
+// Deprecated: Use standard library [slices.Contains] instead.
func Contains(list []string, needle string) bool {
- for _, item := range list {
- if item == needle {
- return true
- }
- }
- return false
+ // TODO(v4): remove package.
+ return slices.Contains(list, needle)
}