diff --git a/docs/docs/apis/openidoauth/scopes.md b/docs/docs/apis/openidoauth/scopes.md index 5d1b7f0789..94655963c8 100644 --- a/docs/docs/apis/openidoauth/scopes.md +++ b/docs/docs/apis/openidoauth/scopes.md @@ -6,13 +6,13 @@ ZITADEL supports the usage of scopes as way of requesting information from the I ## Standard Scopes -| Scopes | Example | Description | -|:---------------|:-----------------|--------------------------------------------------------------------------------| -| openid | `openid` | When using openid connect this is a mandatory scope | -| profile | `profile` | Optional scope to request the profile of the subject | -| email | `email` | Optional scope to request the email of the subject | -| address | `address` | Optional scope to request the address of the subject | -| offline_access | `offline_access` | Optional scope to request a refresh_token (only possible when using code flow) | +| Scopes | Description | +|:---------------|--------------------------------------------------------------------------------| +| openid | When using openid connect this is a mandatory scope | +| profile | Optional scope to request the profile of the subject | +| email | Optional scope to request the email of the subject | +| address | Optional scope to request the address of the subject | +| offline_access | Optional scope to request a refresh_token (only possible when using code flow) | ## Custom Scopes @@ -22,14 +22,13 @@ ZITADEL supports the usage of scopes as way of requesting information from the I In addition to the standard compliant scopes we utilize the following scopes. -| Scopes | Example | Description | -|:-------------------------------------------------|:-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| urn:zitadel:iam:org:project:role:{rolename} | `urn:zitadel:iam:org:project:role:user` | By using this scope a client can request the claim urn:zitadel:iam:roles:rolename} to be asserted when possible. As an alternative approach you can enable all roles to be asserted from the [project](../../guides/manage/console/projects) a client belongs to. | -| urn:zitadel:iam:org:domain:primary:{domainname} | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed | -| urn:zitadel:iam:role:{rolename} | | | -| `urn:zitadel:iam:org:project:id:{projectid}:aud` | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token | -| urn:zitadel:iam:user:metadata | `urn:zitadel:iam:user:metadata` | By adding this scope, the metadata of the user will be included in the token. The values are base64 encoded. | -| urn:zitadel:iam:user:resourceowner | `urn:zitadel:iam:user:resourceowner` | By adding this scope, the resourceowner (id, name, primary_domain) of the user will be included in the token. | -| urn:zitadel:iam:org:idp:id:{idp_id} | `urn:zitadel:iam:org:idp:id:76625965177954913` | By adding this scope the user will directly be redirected to the identity provider to authenticate. Make sure you also send the primary domain scope if a custom login policy is configured. Otherwise the system will not be able to identify the identity provider. | - -> If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request +| Scopes | Example | Description | +|:--------------------------------------------------|:-------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `urn:zitadel:iam:org:project:role:{rolename}` | `urn:zitadel:iam:org:project:role:user` | By using this scope a client can request the claim urn:zitadel:iam:roles:rolename} to be asserted when possible. As an alternative approach you can enable all roles to be asserted from the [project](../../guides/manage/console/projects) a client belongs to. | +| `urn:zitadel:iam:org:domain:primary:{domainname}` | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed | +| `urn:zitadel:iam:role:{rolename}` | | | +| `urn:zitadel:iam:org:project:id:{projectid}:aud` | `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access token | +| `urn:zitadel:iam:org:project:id:zitadel:aud` | `urn:zitadel:iam:org:project:id:zitadel:aud` | By adding this scope, the ZITADEL project ID will be added to the audience of the access token | +| `urn:zitadel:iam:user:metadata` | `urn:zitadel:iam:user:metadata` | By adding this scope, the metadata of the user will be included in the token. The values are base64 encoded. | +| `urn:zitadel:iam:user:resourceowner` | `urn:zitadel:iam:user:resourceowner` | By adding this scope, the resourceowner (id, name, primary_domain) of the user will be included in the token. | +| `urn:zitadel:iam:org:idp:id:{idp_id}` | `urn:zitadel:iam:org:idp:id:76625965177954913` | By adding this scope the user will directly be redirected to the identity provider to authenticate. Make sure you also send the primary domain scope if a custom login policy is configured. Otherwise the system will not be able to identify the identity provider. | diff --git a/docs/docs/examples/call-zitadel-api/go.md b/docs/docs/examples/call-zitadel-api/go.md index 8a20fb9db8..8c685d8462 100644 --- a/docs/docs/examples/call-zitadel-api/go.md +++ b/docs/docs/examples/call-zitadel-api/go.md @@ -31,11 +31,14 @@ go get github.com/zitadel/zitadel-go/v2 ### Create example client Create a new go file with the content below. This will create a client for the management api and call its `GetMyOrg` function. -The SDK will make sure you will have access to the API by retrieving a Bearer Token using JWT Profile with the provided scopes (`openid` and `urn:zitadel:iam:org:project:id:{projectID}:aud`). -Make sure to fill the vars `issuer`, `api`, `projectID `and `orgID` +The SDK will make sure you will have access to the API by retrieving a Bearer Token using JWT Profile with the provided scopes (`openid` and `urn:zitadel:iam:org:project:id:zitadel:aud`). +Make sure to fill the vars `issuer` and `api`. The issuer and api is the domain of your instance you can find it on the instance detail in the ZITADEL Cloud Customer Portal or in the ZITADEL Console. -The projectID you will find in the ZITADEL project in the first organization of your instance and the orgID on the first organization. + +:::note +The issuer will require the protocol (`https://` and `http://`) and you will only have to specify a port if they're not default (443 for https and 80 for http). The API will always require a port, but no protocol. +::: ```go package main @@ -54,43 +57,44 @@ import ( ) var ( - issuer = flag.String("issuer", "", "issuer of your ZITADEL instance (in the form: https://.zitadel.cloud or https://)") - api = flag.String("api", "", "gRPC endpoint of your ZITADEL instance (in the form: .zitadel.cloud:443 or :443)") - projectID = flag.String("projectID", "", "ZITADEL projectID in your instance") - orgID = flag.String("orgID", "", "orgID to set for overwrite example") + issuer = flag.String("issuer", "", "issuer of your ZITADEL instance (in the form: https://.zitadel.cloud or https://)") + api = flag.String("api", "", "gRPC endpoint of your ZITADEL instance (in the form: .zitadel.cloud:443 or :443)") ) func main() { - flag.Parse() + flag.Parse() - client, err := management.NewClient( - *issuer, - *api, - []string{oidc.ScopeOpenID, zitadel.ScopeProjectID(*projectID)}, + //create a client for the management api providing: + //- issuer (e.g. https://acme-dtfhdg.zitadel.cloud) + //- api (e.g. acme-dtfhdg.zitadel.cloud:443) + //- scopes (including the ZITADEL project ID), + //- a JWT Profile token source (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH + client, err := management.NewClient( + *issuer, + *api, + []string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()}, ) - if err != nil { - log.Fatalln("could not create client", err) - } - defer func() { - err := client.Connection.Close() - if err != nil { - log.Println("could not close grpc connection", err) - } - }() - - ctx := context.Background() - - resp, err := client.GetMyOrg(ctx, &pb.GetMyOrgRequest{}) - if err != nil { - log.Fatalln("call failed: ", err) - } - log.Printf("%s was created on: %s", resp.Org.Name, resp.Org.Details.CreationDate.AsTime()) - - respOverwrite, err := client.GetMyOrg(middleware.SetOrgID(ctx, *orgID), &pb.GetMyOrgRequest{}) - if err != nil { - log.Fatalln("call failed: ", err) - } - log.Printf("%s was created on: %s", respOverwrite.Org.Name, respOverwrite.Org.Details.CreationDate.AsTime()) + if err != nil { + log.Fatalln("could not create client", err) + } + defer func() { + err := client.Connection.Close() + if err != nil { + log.Println("could not close grpc connection", err) + } + }() + + ctx := context.Background() + + //call ZITADEL and print the name and creation date of your organisation + //the call was successful if no error occurred + resp, err := client.GetMyOrg(ctx, &pb.GetMyOrgRequest{}) + if err != nil { + log.Fatalln("call failed: ", err) + } + log.Printf("%s was created on: %s", resp.Org.Name, resp.Org.Details.CreationDate.AsTime()) +} + ``` #### Key JSON diff --git a/docs/docs/guides/integrate/access-zitadel-apis.md b/docs/docs/guides/integrate/access-zitadel-apis.md index 21ff8f25b2..7689efd55b 100644 --- a/docs/docs/guides/integrate/access-zitadel-apis.md +++ b/docs/docs/guides/integrate/access-zitadel-apis.md @@ -41,14 +41,14 @@ With the encoded JWT from the prior step, you will need to craft a POST request To access the ZITADEL APIs you need the ZITADEL Project ID in the audience of your token. This is possible by sending a custom scope for the audience. More about [Custom Scopes](../../apis/openidoauth/scopes) -Use the scope `urn:zitadel:iam:org:project:id:{projectid}:aud` to include the project id in your audience +Use the scope `urn:zitadel:iam:org:project:id:zitadel:aud` to include the ZITADEL project id in your audience ```bash curl --request POST \ --url {your_domain}/oauth/v2/token \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ - --data scope='openid profile email urn:zitadel:iam:org:project:id:69234237810729019:aud' \ + --data scope='openid profile email urn:zitadel:iam:org:project:id:zitadel:aud' \ --data assertion=eyJ0eXAiOiJKV1QiL... ``` @@ -75,7 +75,7 @@ With this token you are allowed to access the [ZITADEL APIs](../../apis/introduc * Grant a user for ZITADEL * Because there is no interactive logon, you need to use a JWT signed with your private key to authorize the user -* With a custom scope (`urn:zitadel:iam:org:project:id:{projectid}:aud`) you can access ZITADEL APIs +* With a custom scope (`urn:zitadel:iam:org:project:id:zitadel:aud`) you can access ZITADEL APIs Where to go from here: diff --git a/internal/command/user.go b/internal/command/user.go index f49a7de40b..d31727f150 100644 --- a/internal/command/user.go +++ b/internal/command/user.go @@ -263,7 +263,7 @@ func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteMo return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1d6Gg", "Errors.User.NotFound") } - audience = domain.AddAudScopeToAudience(audience, scopes) + audience = domain.AddAudScopeToAudience(ctx, audience, scopes) preferredLanguage := "" existingHuman, err := c.getHumanWriteModelByID(ctx, userWriteModel.AggregateID, userWriteModel.ResourceOwner) diff --git a/internal/domain/auth_request.go b/internal/domain/auth_request.go index 43a6caafd4..518af1da7f 100644 --- a/internal/domain/auth_request.go +++ b/internal/domain/auth_request.go @@ -151,19 +151,6 @@ func (a *AuthRequest) AppendAudIfNotExisting(aud string) { a.Audience = append(a.Audience, aud) } -func (a *AuthRequest) GetScopeProjectIDsForAud() []string { - projectIDs := make([]string, 0) - switch request := a.Request.(type) { - case *AuthRequestOIDC: - for _, scope := range request.Scopes { - if strings.HasPrefix(scope, ProjectIDScope) && strings.HasSuffix(scope, AudSuffix) { - projectIDs = append(projectIDs, strings.TrimSuffix(strings.TrimPrefix(scope, ProjectIDScope), AudSuffix)) - } - } - } - return projectIDs -} - func (a *AuthRequest) GetScopeOrgPrimaryDomain() string { switch request := a.Request.(type) { case *AuthRequestOIDC: diff --git a/internal/domain/request.go b/internal/domain/request.go index 6f082ace6f..6c405cc513 100644 --- a/internal/domain/request.go +++ b/internal/domain/request.go @@ -4,6 +4,7 @@ const ( OrgDomainPrimaryScope = "urn:zitadel:iam:org:domain:primary:" OrgDomainPrimaryClaim = "urn:zitadel:iam:org:domain:primary" ProjectIDScope = "urn:zitadel:iam:org:project:id:" + ProjectIDScopeZITADEL = "zitadel" AudSuffix = ":aud" SelectIDPScope = "urn:zitadel:iam:org:idp:id:" ) diff --git a/internal/domain/token.go b/internal/domain/token.go index ee2efea48b..7474ea2a32 100644 --- a/internal/domain/token.go +++ b/internal/domain/token.go @@ -1,9 +1,11 @@ package domain import ( + "context" "strings" "time" + "github.com/zitadel/zitadel/internal/api/authz" es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models" ) @@ -20,11 +22,16 @@ type Token struct { PreferredLanguage string } -func AddAudScopeToAudience(audience, scopes []string) []string { +func AddAudScopeToAudience(ctx context.Context, audience, scopes []string) []string { for _, scope := range scopes { - if strings.HasPrefix(scope, ProjectIDScope) && strings.HasSuffix(scope, AudSuffix) { - audience = append(audience, strings.TrimSuffix(strings.TrimPrefix(scope, ProjectIDScope), AudSuffix)) + if !(strings.HasPrefix(scope, ProjectIDScope) && strings.HasSuffix(scope, AudSuffix)) { + continue } + projectID := strings.TrimSuffix(strings.TrimPrefix(scope, ProjectIDScope), AudSuffix) + if projectID == ProjectIDScopeZITADEL { + projectID = authz.GetInstance(ctx).ProjectID() + } + audience = append(audience, projectID) } return audience }