Giter Site home page Giter Site logo

zitadel / oidc Goto Github PK

View Code? Open in Web Editor NEW
1.2K 16.0 124.0 1.57 MB

Easy to use OpenID Connect client and server library written for Go and certified by the OpenID Foundation

Home Page: https://zitadel.com

License: Apache License 2.0

Go 99.54% JavaScript 0.04% HTML 0.42%
oidc oauth2 openidconnect server client go golang openid-connect oauth certified jwt standard library code-flow code-flow-pkce pkce discovery refresh-token relying-party

oidc's Introduction

OpenID Connect SDK (client and server) for Go

semantic-release Release Go Reference license release Go Report Card codecov

openid_certified

What Is It

This project is an easy-to-use client (RP) and server (OP) implementation for the OIDC (OpenID Connect) standard written for Go.

The RP is certified for the basic and config profile.

Whenever possible we tried to reuse / extend existing packages like OAuth2 for Go.

Basic Overview

The most important packages of the library:

/pkg
    /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)
    /oidc              definitions shared by clients and server

/example
    /client/api        example of an api / resource server implementation using token introspection
    /client/app        web app / RP demonstrating authorization code flow using various authentication methods (code, PKCE, JWT profile)
    /client/github     example of the extended OAuth2 library, providing an HTTP client with a reuse token source
    /client/service    demonstration of JWT Profile Authorization Grant
    /server            examples of an OpenID Provider implementations (including dynamic) with some very basic login UI

Semver

This package uses semver for releases. Major releases ship breaking changes. Starting with the v2 to v3 increment we provide an upgrade guide to ease migration to a newer version.

How To Use It

Check the /example folder where example code for different scenarios is located.

# start oidc op server
# oidc discovery http://localhost:9998/.well-known/openid-configuration
go run github.com/zitadel/oidc/v3/example/server
# start oidc web client (in a new terminal)
CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app
  • open http://localhost:9999/login in your browser
  • 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:

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:

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 [email protected])

Features

Relying party OpenID Provider Specification
Code Flow yes yes OpenID Connect Core 1.0, Section 3.1
Implicit Flow no1 yes OpenID Connect Core 1.0, Section 3.2
Hybrid Flow no not yet OpenID Connect Core 1.0, Section 3.3
Client Credentials yes yes OpenID Connect Core 1.0, Section 9
Refresh Token yes yes OpenID Connect Core 1.0, Section 12
Discovery yes yes OpenID Connect Discovery 1.0
JWT Profile yes yes RFC 7523
PKCE yes yes RFC 7636
Token Exchange yes yes RFC 8693
Device Authorization yes yes RFC 8628
mTLS not yet not yet RFC 8705

Contributors

Screen with contributors' avatars from contrib.rocks

Made with contrib.rocks.

Resources

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:).
Versions that also build are marked with ⚠️.

Version Supported
<1.21
1.21
1.22

Why another library

As of 2020 there are not a lot of OIDC library's in Go which can handle server and client implementations. ZITADEL is strongly committed to the general field of IAM (Identity and Access Management) and as such, we need solid frameworks to implement services.

Goals

Other Go OpenID Connect libraries

https://github.com/coreos/go-oidc

The go-oidc does only support RP and is not feasible to use as OP that's why we could not rely on go-oidc

https://github.com/ory/fosite

We did not choose fosite because it implements OAuth 2.0 on its own and does not rely on the golang provided package. Nonetheless this is a great project.

License

The full functionality of this library is and stays open source and free to use for everyone. Visit our website and get in touch.

See the exact licensing terms here

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an " 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.

Footnotes

  1. https://github.com/zitadel/oidc/issues/135#issuecomment-950563892

oidc's People

Contributors

adlerhurst avatar andar1an avatar ashep avatar celian-garcia avatar dependabot-preview[bot] avatar dependabot[bot] avatar eliobischof avatar fforootd avatar hifabienne avatar jcustin avatar jkroepke avatar jmillerv avatar jozuenoon avatar korylprince avatar livio-a avatar mffap avatar michaelholtermann avatar monstermunchkin avatar moximoti avatar muhlemmer avatar muir avatar mv-kan avatar otakakot avatar rohinish404 avatar snowkat avatar stebenz avatar utkuozdemir avatar wenerme avatar wenyxu avatar ydrisr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oidc's Issues

Support the device code flow

Is your feature request related to a problem? Please describe.

When creating CLI or native apps the device code flow is the preferred grant type to get access tokens into a users environment.

Describe the solution you'd like

Examples and logic for supporting the device code flow in this library would be amazing. I would be happy to contribute.

Describe alternatives you've considered

The only golang library I've seen implement the device code flow on github is solid. It's a fairly basic flow and shouldn't be too difficult to implement.

VerifyAccessToken doesn't work because DecryptToken not implemented yet

Describe the bug
VerifyAccessToken doesn't work because DecryptToken has not been implemented yet

To Reproduce
Steps to reproduce the behavior:
To use VerifyAccessToken functionality. it supposed to decrypt the access token and add values in claims. Then claims can be verified based on the validations. since DecryptToken not implemented so it doesn't. work

Expected behavior
VerifyAccessToken to work, and claims have as per the claims in Access token

Screenshots
Screen Shot 2021-11-21 at 1 16 16 pm

Desktop (please complete the following information):

  • OS: Mac OS

`invalid memory address or nil pointer dereference` when accessing UserInfoAddress

Describe the bug
When user info is missing address field it's decoded into userinfo struct where Address field has UserInfoAddress type but nil value.

UserInfoAddress type doesn't expect nil as receiver, so code like info.GetAddress().GetCountry() panics.

To Reproduce
Steps to reproduce the behavior:

  1. Unmarshal userinfo without address
  2. Try info.GetAddress().GetCountry()
  3. See panic

Expected behavior
Probably, returning default values for all fields (e.g. "").

Another (IMO better but breaking) way is to return a pointer. So it will be possible to check value GetAddress() == nil

Additional context
Temp workaround:

func isNil(val any) bool {
	return val == nil || (reflect.ValueOf(val).Kind() == reflect.Ptr && reflect.ValueOf(val).IsNil())
}

addr := info.GetAddress()
if !isNil(addr) {
  // ...
}

proposal: improve logging

Is your feature request related to a problem? Please describe.
Implementing op and rp integrations I had lots of bugs. I only found those bugs by adding 100s of prints to the code.

Describe the solution you'd like
After xop is released (should be in a couple weeks or less), I would like to add logging everywhere. This will require changing many function signatures because either a *xop.Log or a context.Context would need to be passed to every function that wants to log and many functions do not currently have a context parameter.

Xop is the right choice because:

  • It has a mechanism to redact strings and models and thus can, for example, log a JWT but without it's signature.
  • Xop can easily change logging level based on environment variables and thus be silent or minimal when not debugging
  • Xop's performance is very good (better than zap, on par with zerolog).
  • Xop supports tracing and thus can potentially allow allow an authentication process to be followed across servers and time

Describe alternatives you've considered

Xop is not the right choice because:

  • it's young
  • it doesn't have connectors to other loggers yet (coming soon, but not yet)

Additional context

Help wanted for SPA + Code Flow

How do i implement the REST Server Part of the Code Flow Authentication in combination with a SPA ?
Like statet here: https://security.stackexchange.com/questions/129928/oidc-flow-for-spa-and-restful-api

**Authorization Code Flow**

1. User navigates to SPA, which redirects user to IdP to sign in.
2. User signs in (and authorizes the application, if needed).
3. IdP returns user to SPA with Authorization Code.
4. JavaScript code in SPA sends the Authorization Code to a login endpoint on the REST API Server.
5. The REST API Server sends a request to the IdP Server containing the Authorization Code (and usually also a Client ID and Client Secret which identify the REST API Server to the IdP server).
6. The IdP validates the Authorization Code and sends the Access Token and ID Token to the REST API Server.
7. The REST API Server stores the Access Token and ID Token in its memory, and sends its own Session Token back to the SPA.
8. For every request the SPA makes to the REST API Server, it includes the Session Token which the REST API Server gave it. If the REST API Server needs to request resources from another server, it uses the stored Access Token to make that request.

Any help with that? Or hints what to do?

Thansk and regards, fl0w

⚠️ IMPORTANT NOTICE ⚠️ - Relocation of this project

What happens

In order to improve our commitment to ZITADEL with its libraries and tools being open source, we will relocate this repository to the newly created ZITADEL organisation.

When will this be done

Beginning from 27.04.2022 this repository will be newly located here -> oidc

How to migrate

Go Mod

To migrate your go imports please execute the following command in your projects directory:

find ./ -type f -name "*.go" -o -name "go.mod" -print0 | xargs  -0 sed -i '' -e "s/github.com\/caos\/oidc/github.com\/zitadel\/oidc/"

Git Remote

As by Github's documentation we recommend the following:

All links to the previous repository location are automatically redirected to the new location. When you use git clone, git fetch, or git push on a transferred repository, these commands will redirect to the new repository location or URL. However, to avoid confusion, we strongly recommend updating any existing local clones to point to the new repository URL.
git remote set-url origin [email protected]:zitadel/oidc.git

What else

This change will happen in parallel to our release of the version 2 of this library with breaking changes mentioned in #173

unable to Introspect tokens if the issued host is different from the Introspect host

Describe the bug
rs.Introspect method inside req is created to inspect the token and host of this request is always be the host of Introspect endpoint host. As of my understanding token issued host and Introspect endpoint can be different in this case token validation will fail.

To Reproduce

  1. Create a access token for different host from Introspect url host.
  2. try to introspect the token

This will always fail cause token issued host and the introspect different.

Expected behavior
introspect method should allow req host changes as of my understanding.

I am pretty new to this and maybe this is not a bug rather a question.

Incorrect terminology used in README.md

Describe the bug
The front page README.md is using incorrect terminology:

  • It says "Relaying Party" [sic] instead of "Relying Party"
  • It says "Origin Party" instead of "OpenID Provider"

To Reproduce
Steps to reproduce the behavior:

  1. Browse to https://github.com/caos/oidc#features

Expected behavior
According to the OpenID Connect Core specification:

  • RP is "Relying Party"
  • OP is "OpenID Provider"

Screenshots
image

Desktop (please complete the following information):
N/A

Smartphone (please complete the following information):
N/A

Additional context
N/A

rp/mock: go generate fails

When running go generate ./... on in the project root, it fails with:

prog.go:14:2: no required module provides package github.com/zitadel/oidc/pkg/rp; to add it:
        go get github.com/zitadel/oidc/pkg/rp
prog.go:12:2: no required module provides package github.com/golang/mock/mockgen/model: go.mod file not found in current directory or any parent directory; see 'go help modules'
prog.go:14:2: no required module provides package github.com/zitadel/oidc/pkg/rp: go.mod file not found in current directory or any parent directory; see 'go help modules'
2023/02/17 09:44:05 Loading input failed: exit status 1
generate.go:3: running "mockgen": exit status 1

//go:generate mockgen -package mock -destination ./verifier.mock.go github.com/zitadel/oidc/pkg/rp Verifier

There are 2 issues here:

  1. The path to the rp package has changed to github.com/zitadel/oidc/pkg/client/rp;
  2. There is no type Verifier in neither the client or rp package;

Does somebody know what happened? What would be the correct type and package path?

Questions about the example

Hey there,

My knowledge when it comes to hosting an OIDC provider is fairly limited, and I have a couple of questions I hope you'll be able to answer.

For the sake of verbosity, I plan on integrating the server implementation of this library in an existing application. It will therefore live alongside multiple other handlers. Furthermore, I'm only interested in the app client implementation, though I'm not 100% sure what flow each of the folder represent.

  1. Why is there a /healthz handler registered by the server example? Is it because the server is meant to be run as a standalone application (albeit with some modifications to support endpoints like userinfo, etc.)

  2. What exactly does the t variable represent here?:
    https://github.com/caos/oidc/blob/eb10752e485ced36bd996bcb290c6e617f5ea449/example/internal/mock/storage.go#L118-L122

  3. Why is this function called HandleCallback? Shouldn't the callback technically be on the client side? From what I understand, it would be more appropriate to name it something like HandleLoginAttempt?
    https://github.com/caos/oidc/blob/eb10752e485ced36bd996bcb290c6e617f5ea449/example/server/default/default.go#L68

  4. If the statement above is accurate, then I think the handler should also be in charge of verifying the credentials (username/password) of the user. If so, how does one convey that the verification of the credentials was successful and convey that data? From what I see, it's just redirecting to another path
    https://github.com/caos/oidc/blob/eb10752e485ced36bd996bcb290c6e617f5ea449/example/server/default/default.go#L71

  5. Still in the same function/handler above, assuming we swap the input client for username and add an extra input password in the HandleLogin handler, we would get something like:

func HandleCallback(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  username := r.FormValue("username")
  password := r.FormValue("password")
  isUsernameAndPasswordValid := DoSomethingToValidateCredentialsHere(username, password)
  if !isUsernameAndPasswordValid {
    http.Error(w, "invalid username or password", http.StatusUnauthorized)
    return
  }
  // At this point, we've verified that the credentials are valid, but we have to return an id..?
  http.Redirect(w, r, "/authorize/callback?id="+id, http.StatusFound)
}

instead of
https://github.com/caos/oidc/blob/eb10752e485ced36bd996bcb290c6e617f5ea449/example/server/default/default.go#L68-L71
which prompts the question, is the id passed as query parameter in the redirect function expected to be some kind of session id?

I think a diagram -- not just of a typical IODC provider, but of the exact endpoints used in each client example -- would greatly help people in understanding how it works. Normally, I'd offer to do it, alas, like I said, I'm not really knowledgeable when it comes to the implementation of an IODC provider 😅 If I end up getting the gist of it, I'll definitely try to help out with the documentation though.

`kid` is missing after in the issued JWT access token

Describe the bug
kid is missing after in jwt access token issuing

To Reproduce
Steps to reproduce the behavior:

  1. Create a client with op.AccessTokenTypeJWT access token type
  2. Provide a key with X.509 cert
  3. Follow the oidc.GrantTypeCode flow
  4. The jwt issued by /oauth/token endpoint does not contains kid in its header

Expected behavior
Issued access token should contains kid in its header

missing state in redirect of implicit flow

Describe the bug

as described here: https://discord.com/channels/927474939156643850/927866013545025566/981120399658082334

if I create the default quickstart react app, and using either Zitadel v1, or v2, setup a client to allow id_token, then configure the app to use id_token response type instead of code, the app doesn't work because the redirect from Zitadel doesn't include the state that the client

Expected behaviour

The redirect should include the state field.

Support ES256

As a developer I want to be able to use this library with id tokens that are signed with "ES256" instead of RS256.

Acceptance Criteria

  • I can use tokens signed with ES256

Flow example - implicit flow

Hi all,

Im trying to implement a client using the Implicit flow.
But the question is also more general, I cant find how and where to set up the response_type so that I can change the flow to implicit.

To me its seems that the default flow is the authorization_code flow.

Thanks,
Zarko

client.Discover: Issuer matching is too strict

Describe the bug
Currently client.Discover will fail if the provided issuer doesn't match the returned issuer. However this validation is too strict as it will fail with oidc.ErrIssuerInvalid even if i provide https://my.oidc.com in the function parameter but the returned value will be https://my.oidc.com/ (real use case I encountered).

Expected behavior
I expect discover to succeed if the returned issuer "starts with" the provided issuer or have an option to disable this verification.

OAuth 2.1 compliance regarding PKCE and client secret

Is your feature request related to a problem? Please describe.
The current definition of OAuth 2.1 expects a client secret when a confidential app is used in combination with PKCE.

Describe the solution you'd like
Check of client secret if PKCE flow is used when app is typed as confidential.

Describe alternatives you've considered
Split of compliance with a different version, to bet at least different from the new version of OAuth.

Additional context

https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-09#authorization_codes

RP configuration for handlers with URL options to call RS

As a developer I want to be able to configure additional URL parameters for the AuthURLHandler and CodeExchangeHandler so that I don't have to implement and extend my own handlers.

Example: login_hint as parameter, so that the OIDC package can also be used to start a flow with a given username

Acceptance Criteria

  • I can define some options which are used to call the RS
  • Handler is usable without separate implementation

example server isn't enough for testing

Is your feature request related to a problem? Please describe.

I'm using the oidc library as both client and server. When using it as a client, I need a server to test against (not my own, since my own is a proxy). The example server isn't configurable enough to use in tests.

Describe the solution you'd like

What I would like to do is make changes to the example server so that it's more robust and has Go interface.

Describe alternatives you've considered

My concern is that the purpose of the example server is mostly to be documentation. Anything I do to it to give it additional features will obscure the documentation it provides.

The features I need are:

  • set up / tear down from inside tests. That means moving most of what's in the main package to a sub-package and then just calling it from main.
  • thread safe. Adding locks isn't hard, but it complicates the code a little bit
  • override the user storage. In tests, I need control over the set of users so I need a public interface for user lookup that I can provide from my own tests. This requires restructuring the test code a little bit inside example/server/internal. That restructuring again complicates the code without improving it's function as an example

Additional context

My basic question is: if I do this work as described above, would it be merged or would it be rejected because adding features makes the example not quite as good documentation as it was?

I've already started. If the answer is: not merged, then I'll simply copy the code into my own repo.

AuthRequestByID Missing field

Is your feature request related to a problem? Please describe.
After the user logs in, call back /authorize/callback?id=。 At this time, the ID is authrequestid. I can't know the logged in user ID. Therefore, the relationship between the logged in user ID and authrequestid needs to be implemented by yourself. This may be a problem

Describe the solution you'd like
In / authorize / callback? Id = add a parameter to the interface, such as / authorize / callback? Id = & uid =, where uid is the currently logged in user ID.
At the same time, the interface authrequestbyid (context. Context, string) (authrequest, error) adds the parameter authrequestbyid (context. Context, ID, uid, string) (authrequest, error)

Concurrency bug in NewOpenIDProvider()

I built an OpenID provider that works from within an inetd-like scenario: the provider interacts with stdin and stdout, and it expects its parent process to create a socket before connecting this socket to the child's (i.e., the provider's) stdin and stdout. This seems to expose a concurrency bug in NewOpenIDProvider(). The symptom of this is an "unsupported signing algorithm" error later in the code.

NewOpenIDProvider() contains the statement go storage.GetSigningKey(ctx, keyCh), but it seems to perform no synchronization that would ensure the value is updated before it is later used. I think my use caused the oidc engine to rely on the signing key before this Go routine set it by way of keyCh.

I worked around this by adding a runtime.Gosched() immediately after my provider calls NewOpenIDProvider(). This seems to fix things in my odd case.

Error handling

As a developer i want to get the full error so that i am able to identify the potential cause of it.

  • The error cause (parent field) must be returned in the error response
  • Review where errors should be handled

More examples with more detailed comment will help!

Is your feature request related to a problem? Please describe.
Big thank for your efforts on this great project!
I want to build an OP based on this project, but the OP example is too fuzzy for me to make the features I want.

Describe the solution you'd like
Add or modify OP examples:

  1. login with password, so it can help me safely processing login.
  2. handle multiple client_id, so it can help me know how to handle multiple clients.
  3. more detail comments on the example code, so I can know where I should modify and where I can keep as it is.

Appreciate your help!

Example for api instrospection

In the example/client/api i always get 401 from token instrospection when calling /protected.

resp, err := rs.Introspect(r.Context(), provider, token)
if err != nil {
	http.Error(w, err.Error(), http.StatusForbidden)
	return
}

The step:

  1. Get an access token from example/client/service
  2. Pass the token as Authorization Bearer to /protected.

Did i miss something?

Example storage.go doesn't return x5c, x5t props in keys endpoint

Describe the bug
Hey folks.
We are experimenting with embedding Zitadel OIDC in NetBird. We took the example project as a foundation, and it seems like everything is alright, except for one point related to the JWT keys endpoint.

Our management service uses the discovery endpoint "/.well-known/openid-configuration" to fetch the configuration. Then it takes jwks_uri and fetches JWT keys. For example, the jwks_uri is this endpoint http://localhost:9998/keys.
This endpoint returns the following content missing x5c and x5t properties:

{
	"keys": [{
		"use": "sig",
		"kty": "RSA",
		"kid": "id",
		"alg": "RS256",
		"n": "5eEovI1i87BBhBzMsHt1xKbDDpwFkTUgvJBkUQVPTMeWGNDTIyo0ithlfaQR6qSWhX9U752F0IMGo3pQHXnHVs91Qa1y3PeFbk-3Lc1KtmrFWzrXJ46j0sKpB9rbLEO1qT2HZalt-w-B2UAU0suezjWZioRkpDacnbPT_VRaDa3Mlu5MBDea-YBH_uuRrAa-luA8dVLtU_8Cd3tp-W7UoO2qdqJuiAXo9pKOMVhuZQUYqLuYZBxNiAzyy5NcRYkoikkicc2no4NAApWc3A24d6P8-UhFw91oRysU5LwW34eUB1sbCO5VomagLkXpeH2HdaireRLZI_HQLP-fqz_EcQ",
		"e": "AQAB"
	}]
}

It looks like that the problem is here - these fields are not set.

return &jose.JSONWebKeySet{

What would be the way to set them?

To Reproduce
Start

  1. cd example/server
  2. go build
  3. ./server
  4. Access http://localhost:9998/keys in your browser
  5. x5t and x5c props are not present in the response

Expected behavior
x5t and x5c props are present in the response

P.S. Not sure if this is a bug at all. Any help on that would be very much appreciated.

No method to get all all claims

Is your feature request related to a problem? Please describe.
In our service we need to get all claims. Currently, UserInfo allows getting only known claims by name.

Describe the solution you'd like
Just additional method GetClaims() map[string]any in UserInfo interface

Access token validation

Is your feature request related to a problem? Please describe.
I'm trying to use the library not only for the first login but also to keep the access token in a cookie and then verify it on every request. However it seems there are only methods to verify the id token (which I don't need) and not the access token give a provider.

Describe the solution you'd like
Have a function that given an access token, validates it against the provider keys

Describe alternatives you've considered
Use the jwt package to verify it but the signing keys are private variables on the oauth config

RP client key as data instead of path to file

We have client key stored in a database as a string. We want to pass it as an option when create an RP instance. However, there is only one option - WithClientKey. It accepts path to file. So we have to store key data to the fs and then pass a path to the temporary file.

There is already a function ConfigFromKeyFileData, we just need an Option that accepts raw data and sets rp.signer from raw config.
It can be something like WithClientKeyRaw(key []byte) or WithClientKeyConfig(cfg *client.KeyFile)

Missing Copyright notice

As of revision fca6cf9, the LICENSE file has an incomplete copyright notice:

Copyright [yyyy] [name of copyright owner]

I've developed derivative work based on the example/server code and I need to know the correct copyright holder to give credit and comply with the Apache License.

By the way, thanks a lot for making all this work available under an open-source license.

Zitadel docs link broken

Broken link
The link at https://github.com/zitadel/oidc/blob/main/README.md?plain=1#L81 is redirected to https://zitadel.com/docs/docs/guides/integrate/login-users, which does not exists

To Reproduce
Steps to reproduce the behavior:

  1. Go to https://github.com/zitadel/oidc/blob/main/README.md#resources
  2. Click on OIDC/OAuth Flow in Zitadel (using this library)
  3. See error

Expected behavior
The link leads to the correct page. I guess it should lead to https://zitadel.com/docs/guides/integrate/login-users

Implement device authorization grant?

Is your feature request related to a problem? Please describe.
I want to authenticate my smart device which has no web browser with my phone. The https://oauth.net/2/device-flow/ flow fixes this.

It's quite commonly used in IoT and smart TVs. It's also used by some cloud CLIs (like awscli) to implement single sign on

Describe the solution you'd like
Implement the https://oauth.net/2/device-flow/ flow

Describe alternatives you've considered
none

Additional context
https://oauth.net/2/device-flow/

Multiple issuer support

The library should be able to handle multiple issuers on a single OpenID Provider.

Acceptance Criteria

  • whenever possible changes are done without breaking current interfaces / implementations of this library
  • issuer can be selected based on http request / context
  • default implementation of the OP interface supports single and multi mode

go run ... hangs indefinitely

Describe the bug
When I execute the command go run github.com/zitadel/oidc/example/server, the process seems to hang indefinitely. I don't see anything happening

$ go run github.com/zitadel/oidc/example/server
INFO[0000] signer exchanged signing key                  caller="/Users/pratikbansal/Downloads/zitadel/oidc/pkg/op/signer.go:83"
<hangs ...>

To Reproduce
Steps to reproduce the behavior:

  1. Run the command go run github.com/zitadel/oidc/example/server

Expected behavior
The server should have started

Desktop (please complete the following information):

  • OS: [e.g. iOS] MacOS
  • Browser [e.g. chrome, safari] NA
  • Version [e.g. 22] NA

rp.AuthURLHandler: stateFn could provide the current request as argument

Is your feature request related to a problem? Please describe.

The client/rp provides a way to save a state during the login flow. To do this, the rp.AuthURLHandler func accepts a stateFn func() string argument.

However I would like my state to be dependent on the request (to extract some request parameters, path or query).

Describe the solution you'd like
Change the argument to accept a func(*http.Request) string instead.

Describe alternatives you've considered
Wrap the rp.AuthURLHandler into a handler which provides a custom stateFn depending on the request.

Additional context
This would be a breaking change (trivial to fix for the caller, but still breaking, strictly speaking).

To make it non breaking, one could add a new func AuthURLRedirect (or some other name) and deprecate AuthURLHandler:

//AuthURLRedirect extends the `AuthURL` method with a http redirect handler
//including handling setting cookie for secure `state` transfer
func AuthURLRedirect(stateFn func(*http.Request) string, rp RelyingParty) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		opts := make([]AuthURLOpt, 0)
		state := stateFn(r)
		...
	}
}

//AuthURLHandler extends the `AuthURL` method with a http redirect handler
//including handling setting cookie for secure `state` transfer
//
//deprecated: use AuthURLRedirect
func AuthURLHandler(stateFn func() string, rp RelyingParty) http.HandlerFunc {
	return AuthURLRedirect(func(*http.Request) string { return stateFn() }, rp)
}

I would be willing to craft a PR I you think that this is a good idea.

Enhance id_token with custom claims

Is your feature request related to a problem? Please describe.
My login information is provided a legacy system, not compliant to any standard. It's two-phased and reads quite usual:

  1. user logs in via username/password, the legacy system returns a secret (some token, but not OIDC compliant)
  2. now I can call the legacy system with that secret and reveal things like user name, mail address, roles etc.

I'm able to "wrap" this legacy login into OIDC compliant id_token with zitadel/oidc, by implementing these to steps within CheckUsernamePassword(username, password, id string) error from the example. ✅

But later, I need the original token from step 1 to call other APIs from the legacy system.
In addition, I would like to add other information from the two calls as well, like some user roles that are defined in that legacy system.

What I'm trying to achieve is to transport the original token from the login to my backend system via the id_token.

I was already able to append the information the implementation of the op.AuthRequest interface.

Describe the solution you'd like
Currently, during the login process, op.CreateIDToken() is called.

This calls OPStorage.SetUserinfoFromScopes(). In the example in example/server/storage/storage.go this call fills firstname, phone etc..

I would suggest to extend that call so it contains the op.AuthRequest from op.CreateIDToken. Then I could copy the information from that request to the id_token.

This must be done backwards compatible, of course.

Alternatively, a second call function to copy "custom attributes" might be a solution.

Describe alternatives you've considered
I could save the original token in some persistent store, so that my backend systems could fetch it from there once its needed, but this would break the "self containment" of the id_token, at least somehow.

Additional context
I'd be happy to provide a PR, but maybe there are other ideas or even vetos against such a feature.

Support for client_credentials (server)

Is your feature request related to a problem? Please describe.

The server implementation of /authorize doesn't support the "client_credentials" grant type yet

{
    "error":"unsupported_grant_type",
    "error_description":"client_credentials not supported"
}

I'd like to make use of the client_credentials grant so that I can implement a solution that allows applications to request service tokens.

The application client would use the "client_credentials" grant to receive a token with a list of scopes that the client has requested and is allowed to use.

In a multi-service architecture this is very useful because it allows services to request service tokens that they can then use to call other APIs.

Describe the solution you'd like

The server library can implement support for the client_credentials grant by:

  1. calling storage.AuthorizeClientIDSecret
  2. calling storage.GetClientByClientID
  3. calling client.IsScopeAllowed for each requested scope

I'm hoping this would be enough for an MVP solution.

How to set prompt on authorize request

Is your feature request related to a problem? Please describe.
Trying to set a prompt value in authorization request

Describe the solution you'd like
Allow WithPrompt to be included in the URL produced by AuthURL() inside rp.AuthURLHandler()

Describe alternatives you've considered
New to the language but couldn't see an obvious way to inject the prompt AuthURLOpt into the authorization request

Additional context
None.

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


Missing package.json file.

A package.json file at the root of your project is required to release on npm.

Please follow the npm guideline to create a valid package.json file.


Good luck with your project ✨

Your semantic-release bot 📦🚀

Starting an OP with an Issuer URL that contains a path breaks the endpoint handling

Describe the bug
Starting an OP with an Issuer URL that contains a path breaks the endpoint handling.

To Reproduce
Steps to reproduce the behavior:

  1. Apply the following patch:
diff --git a/example/server/default/default.go b/example/server/default/default.go
index 7edaf2e..1877218 100644
--- a/example/server/default/default.go
+++ b/example/server/default/default.go
@@ -17,7 +17,7 @@ func main() {
 	ctx := context.Background()
 	port := "9998"
 	config := &op.Config{
-		Issuer:    "http://localhost:9998/",
+		Issuer:    "http://localhost:9998/path/to/my/op",
 		CryptoKey: sha256.Sum256([]byte("test")),
 	}
 	storage := mock.NewAuthStorage()
@@ -26,7 +26,7 @@ func main() {
 		log.Fatal(err)
 	}
 	router := handler.HttpHandler().(*mux.Router)
-	router.Methods("GET").Path("/login").HandlerFunc(HandleLogin)
+	router.Methods("GET").Path("/path/to/my/op/login").HandlerFunc(HandleLogin)
 	router.Methods("POST").Path("/login").HandlerFunc(HandleCallback)
 	server := &http.Server{
 		Addr:    ":" + port,
@@ -68,5 +68,5 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) {
 func HandleCallback(w http.ResponseWriter, r *http.Request) {
 	r.ParseForm()
 	client := r.FormValue("client")
-	http.Redirect(w, r, "/authorize/callback?id="+client, http.StatusFound)
+	http.Redirect(w, r, "/path/to/my/op/authorize/callback?id="+client, http.StatusFound)
 }
  1. Start example OP:
CAOS_OIDC_DEV=1 go run github.com/caos/oidc/example/server/default

Expected behavior

  1. Now that I have my OP running on http://localhost:9998/path/to/my/op, I expect the Discovery Endpoint to be:
curl http://localhost:9998/path/to/my/op/.well-known/openid-configuration <- 404 Not Found

but instead it does not contain the path of my Issuer URL:

curl http://localhost:9998/.well-known/openid-configuration <- 200 OK
  1. The endpoints in the Discovery Endpoint response are indeed correct, but when I try to curl them I get 404 Not Found:
curl http://localhost:9998/.well-known/openid-configuration | jq

{
  "issuer": "http://localhost:9998/path/to/my/op",
  "authorization_endpoint": "http://localhost:9998/path/to/my/op/authorize",
  "token_endpoint": "http://localhost:9998/path/to/my/op/test",
  "introspection_endpoint": "http://localhost:9998/path/to/my/op/oauth/introspect",
  "userinfo_endpoint": "http://localhost:9998/path/to/my/op/userinfo",
  "revocation_endpoint": "http://localhost:9998/path/to/my/op/revoke",
  "end_session_endpoint": "http://localhost:9998/path/to/my/op/end_session",
  "jwks_uri": "http://localhost:9998/path/to/my/op/keys",
  "scopes_supported": [
  (...)

curl http://localhost:9998/path/to/my/op/authorize <- 404 Not Found

Screenshots
N/A

Desktop (please complete the following information):
N/A

Smartphone (please complete the following information):
N/A

Additional context
N/A

JWT Signature Validation: missing `kid` leads to invalid key error

Hi, I'm currently implementing an OIDC RP in Photoprism. So first of all, thank you for this great library!

Describe the bug
No matching JWK found, if no kid header present in JWT but RemoteKeySet provides a single key with non-empty kid value. In this case we get "invalid key" error (pkg/client/rp/jwks.go:91)

Expected behavior
Either allow absence of kid value in this case and try verification, improve error reporting as the key is valid but could't be found, or automatically add kid to both, the remote key set and the corresponding JWT, if it's not provided by storage implementation.

kid values are not a MUST, if OP does not provide multiple keys:

  1. https://openid.net/specs/openid-connect-core-1_0.html#Signing
  2. https://datatracker.ietf.org/doc/html/rfc7515#section-6

Device authorization grant

As a developer I want to be able to use the device authorization grant for authentication, to be able to authenticate my devices.

Acceptance Criteria

  • I am able to authenticate with device authorization grant
  • Implementation follows the standard https://oauth.net/2/device-flow/
  • I am able to authenticate my device
  • I am able to use it in the OP
  • I am able to use it in the RP

Additional Context
Reported issue: #141

GetClaim() returning interface is awkward to use. Use output parameter instead?

Is your feature request related to a problem? Please describe.

The following code is awkward as I need to guess that the structure that GetClaim() returns is map[string]string.

		val, ok := introspectionResponse.GetClaim("cnf").(map[string]string)
		if !ok {
			http.Error(rw, "", http.StatusUnauthorized)
			return
		}

		expectedClientCertificateHash, err := base64.URLEncoding.DecodeString(val["x5t#S256"])
		if err != nil {
			http.Error(rw, "", http.StatusUnauthorized)
			return
		}
		if !bytes.Equal(clientCertificateHash[:], expectedClientCertificateHash) {
			http.Error(rw, "", http.StatusUnauthorized)
			return
		}

Describe the solution you'd like

I'd prefer GetClaim() to take an output parameter instead. so that I can marshal safely to my own struct type:

func (r IntrospectionResponse) GetClaim(key string, out interface{}) error {...}
type ConfirmationClaim struct {
        CertificateThumbprint string `json:"x5t#S256"`
}

var cnf ConfirmationClaim
err := introspectionResponse.GetClaim("cnf", &cnf)
if err != nil { ... }
expectedClientCertificateHash, err := base64.URLEncoding.DecodeString(cnf.CertificateThumbprint)
if err != nil { ... }
if !bytes.Equal(clientCertificateHash[:], cnf.CertificateThumbprint {
	http.Error(rw, "", http.StatusUnauthorized)
	return
}

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Failed userinfo unmarshal with AWS Cognito due to email_verified being text field.

Describe the bug
AWS Cognito returns userinfo with email_verified field as text.

"userinfo failed: failed to unmarshal response: json: cannot unmarshal string into Go struct field .email_verified of type bool..."

To Reproduce
Steps to reproduce the behavior:

  1. Configure AWS Cognito with the example from the examples folder.
  2. Try to log in.

Expected behavior
Should read userinfo.

return concrete types of Claims, using generics

This change proposes the use of concrete struct types for Claims and descendant types, instead of interfaces. By usage of generics, we can let callers replace the standard types with custom type that carry additional properties. This would be a breaking change for the next branch.

rp package

In the rp package the following function will change their signature:

func VerifyTokens[C oidc.AccessTokenHashGetter](ctx context.Context, accessToken, idTokenString string, v IDTokenVerifier) (claims C, err error)
func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v IDTokenVerifier) (claims C, err error)

op package

In the op package the following functions will change their signature:

func VerifyIDTokenHint[C oidc.Claims](ctx context.Context, token string, v IDTokenHintVerifier) (claims C, err error)
func VerifyAccessToken[C oidc.Claims](ctx context.Context, token string, v AccessTokenVerifier) (claims C, err error)

oidc package

Above type parameters between square brackets ([]) define the minimum set of methods required to check validity of token claims. Aka, constraints. The existing Claims interfaces will be reused and supplemented with 2 methods:

  1. GetSignatureAlgorithm() jose.SignatureAlgorithm migrated from IDTokenClaims
  2. SetSignatureAlgorithm(jose.SignatureAlgorithm) migrated from ClaimsSignature

Although both methods are not required in all use-cases of Claims all the current existing implementations of Claims currently carry the signatureAlg field.

A new interface AccessTokenHashClaims with the GetAccessTokenHash() string method migrated from IDTokenClaims . Finally oidc will carry the following interface definitions, used as constraints:

type Claims interface {
	GetIssuer() string
	GetSubject() string
	GetAudience() []string
	GetExpiration() time.Time
	GetIssuedAt() time.Time
	GetNonce() string
	GetAuthenticationContextClassReference() string
	GetAuthTime() time.Time
	GetAuthorizedParty() string

	// new methods
	GetSignatureAlgorithm() jose.SignatureAlgorithm
	SetSignatureAlgorithm(jose.SignatureAlgorithm)
}

type AccessTokenHashClaims interface {
	Claims
	GetAccessTokenHash() string
}

The following interfaces can now be removed (their names will be reused):

  • AccessTokenClaims
  • IDTokenClaims
  • UserInfo
  • UserInfoProfile
  • UserInfoEmail
  • UserInfoPhone
  • UserInfoAddress
  • UserInfoSetter
  • UserInfoProfileSetter

In place, we will export the currently private struct types by taking the above names.

Example usage

If users want VerifyIDToken to return the predefined IDTokenClaims struct type:

claims, err := VerifyIDToken[*oidc.IDTokenClaims](ctx, token, v)

Or if their token caries additional claims, they can extend the type by struct embedding to gain direct access to those claims:

type customClaims struct {
	oidc.AccessTokenClaims
	Foo string `json:"foo"`
	Bar string `json:"bar"`
}

claims, err := VerifyIDToken[*customClaims](ctx, token, v)

Parse Auth0 `updated_at`

Is your feature request related to a problem? Please describe.
Using the client example app with auth0 and profile scope leads to this error:

failed to exchange token: json: cannot unmarshal string into Go struct field .updated_at of type int64

Describe the solution you'd like
Like for aws incognito in this issue, zitadel/oidc could be able to parse also non-compliant fields used in the wild.

Auth0 uses RCF3339. Example of field:

"updated_at": "2021-05-11T21:13:25.566Z"

Describe alternatives you've considered
Removing the profile scope is a workaround.

Additional context

I marked this as a feature since it is a non-complaint implementation of auth0 and not a bug on zitadel/oidc

Related to #137

JWT access tokens for jwt profile

As a developer I want to be able to receive a JWT access token when using JWT profile grant.

Acceptance Criteria

  • I am able to define what kind of token I receive when using JWT profile

Addtional Context

  • currently hard coded to CreateBearerToken
  • needs TokenType (on RP we have Client interface for that)
  • probably easiest way is to add an additional interface to return the type

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.