oidc

package module
v0.0.0-...-7cc9152 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 1, 2024 License: MIT Imports: 15 Imported by: 0

README

Go OIDC MLE

GoDoc

This library wraps oauth2 and go-oidc libraries and implements OIDC authorization code flow with optional encrypted request object. The Finnish Transport and Communications Agency Traficom mandates message level encryption on electric identification and trust services. More information can be found from the Requlation 72 on electronic identification and trust services.

Examples

A simplified examples implementations of authorization code flow using Signicat as a provider can be found from examples directory.

To run the example using MLE:

cd examples/mle
go run main.go

To run the non-encrypted example:

cd examples/normal
go run main.go

Usage

Configuration is defined as follows. The example uses Signicat's demo account credentials and JWK. For real implementation you'll need your own credentials and key. The public key must be sent to the servicer provider.

// These credentials are taken from here: https://github.com/signicat/OIDC-MLE/blob/master/py-ftn-example/ftn-mle-example.py
// For real implementation you'll need your own credentials and JWK.
config := oidc.Config{
    ClientId:     "demo-ftnenc",
    ClientSecret: "mqZ-_75-f2wNsiQTONb7On4aAZ7zc218mrRVk1oufa8",
    Endpoint:     "https://preprod.signicat.com/oidc",
    RedirectUri:  "https://localhost:5000/redirect",
    LocalJWK:       `{ "d": "Na7lzZR8gTmiJjOnrSew49tT8Qxl7-wFEJAk8_IAKmS1KidtNrNxt5GgBsy7Uksk0EXwYmbxLY7ke_yvGNtDTAaR71VWJyTDYJjiu-D-cMrRWGxLUtf0SDQtuf5_7rVNikmuUgxtaNZowstBZog-W8QIpGv7nvfOKchFK-Cf92ApWWU6DH3vN60TQtk9f8e_XLM4Yy2iBEghU58VNegb8mS9Bg-WfiG8Bf8opjj2IxlssqK98AlXPIZ-T-Xar6D9SkOVYTuracOoxSQjOEKHVCtluGQRinP3yxAQvF81ZPp2zO7LbSx2NRB4h2DzcUXSnMbY2PXgw4Sqs7QlJ7miKgrFyseRgikzZNDLv-8jhujkjRfAZ3VZFPy5-LKtG1uLf8erwwLedCqg9ClTLiXMG05uogdXIB8hYjP04ZWPNR_hQwKAEo3yFsS0SSMBOO4ANjc_uzQf7xmnKei0imDfJcufMFCvPuT_F4x6xJzi_DSLOW8s7KDFvFBTBgnTsoVHIAWDXGXM9iebLx26NwgtUcclfm2hidcsuJnS4Qyx9r-AHjxNH7uVNZP3eyjXqH4jrmweLzOGpSuLIGiXfAi7aVFISH5dD4eaq-zkxZgV-Vs8iRD8TlhYb7ETYxM71fw3Ga-rp9hAHY2_pHz3iCs3yIL08u6CGqe6udB10WmTdjk", "dp": "VYi8AKFAbw0yu5xZcf8MKwQwVSCIqZyw7gZDaz5Exz00XKHVWKlUdvqQH52e9GYW84rqdhCINcXctEnT9kfrUJRp6sg40aFWSfZNGvN0ZlwgHsuk8BKXdD0k8evgEH4iomHk5V6b8Au8ilJN3JlI3mW7ZM4aHqODfPXoNAAwHXNX24hnX3on3Y9xZvEoGZKn4WnU7rGZjcsIYphy3IGfIe0BlZYGTHnteAFjsK0ODXoGXSh2ZvhiDKO6fl57lS_h43i5gLsIOtM-T9uGFFe681h4OcM3HQNcYnwvl0RpdKXIKhVn54w7yKc1e3x6bEO5nj0ZPFwAbLWDZ0ljv_SpOw", "dq": "cqloF7Ips92f75WR2xHAuM7GmpywEWZjdiwbHqDQ79cLFbfQxO99J9kpC9kjTRE4T21OdpHOBtEIQYF8mroEHNtI9guBR1sQwMxx_DHyyJ0M1HHrzBawQr9DqqmqfHNkPCLetwv2E0sOd90CvUU6zL9p0f-Npn4-l1r7KsSAn2w5oDy4fb0ZAn4Lc4GtISYNV9SX9rpZN83WlF1oOzOWenTwiWrQneicRdM5L7HxWBs-FQQX5oi32xSf3chwy9o2po2DUD3Ess5BH-Y0lmDH6hEufwHbKRpKzWLxhZwa0BkbFL68ypbeWK-dUNdh5HCCNup0IpCgP1-_6PnQU-vw9Q", "e": "AQAB", "kty": "RSA", "n": "psPFRnGgt4wJK--20KG0M_AgL2B-J0Q4Nrd3duq0lt2kXwtD5MdAmpWpPncQgMzqVT3IyuEjFjHZRw-tv025DbK6PO4k3sZhQwWJjZGte7nKuHzJkQ7tR0ub2DOq4Sg6iBDmBFQ00wotCIfcAbgBT4WLWFu8ne9K4GUjz3vtUCALLryWJeIriJnNl7kKxo8BhbEp567PmECfill9RpPkgm3bp6s2GqAtIwWss6hYY02GPm_cssFwLl_fRBzQcFxg30i3oMgg-Xj5flewEC8sdPXdzXg9PJTLmppfKdnYtgPCTR8a2mTgy_B8vXXrkX636qk_FaT9C0QWxMg6fII_5vqRdx65uAVWqc69bm0ikSz_PgnK5flkwLRQr4D5CvZNCw7xngrEBTP42O0mjtbQJZPYzF3__pdpwqli1Ja1WNEC0EZtzi_2xs7rn07qVv2ZeQ0mObp4gs2uyflQZ-6Mv7S2MnJ00Bn7M_kl6S9a1jRHQDnCe61yfgQr8oGvfI7jaiN-8IMphzdkpK4nO4euWk8M5XQFpIorVyLT2RtIUQlA4L6GQBBuixZxI7nt2AA9ZA4J5cTukYGqT908NJ3g8HEpbWvuZ8kFOXAVi8EJqN9OFDXB5qPDfXFZ6lH7-UmYPKLOjrscX9LUSz_Onu65SVJlylHqorkK0mVOQgo7oaM", "p": "00FDBBfSFIbQYJ8yOCJ7c6ZPLmJxQ7_Fch01KdHJvKjKinb5dDtJMxgZzKwPudBajJWE4ucVMuRYRv4QMJWXov6CaLKN-SHkMFIwWMN-UJAVGT7e_iIq1_BrvFvTeFY9zshpuyFiP4lDNzPH1xX2aD0lCt42l-1rfScm1LIO0LYy1Qqma6m-aaKLAcBpr-6SM3A7-YqNVP3enZevPTz6rgZ_boKICVdR-a3vLNb5w1sP_18I3Fcb0vGCsoxuNh46DaDdSs2jkwPmIrra040vstoXHjOLzlubrrH69WqkbNtHf1DRcKgh7fzgHwuzovC6Bn142cdCmr9aLyVgExFUNw", "q": "yhYlTst5WmxYynEtYU9GBqysQnjJSh1gDKocbJv_7AHdfIMnK8tHdqEByj9DPgao76yZt1fGSN9v1B3PhVYYrhdLvtksdYjUgnu0vjtg7kHsDxwY6H4nZykxWr1tjcWHHmcUnlWU_vtkg1pES8_WJ-dtH0IYe0luPRqVqs8YYKL6He-pRbPj4YJJ6KtYgYFpSKbS3hGHDeEo_Bwz9-cP6Q6NxJwgeOZz8BtryHo4gh77RapZcpxH320Fw993xYewpAt_Bi7OqasH8-DwxMSxK-VuAjgfokxZMX2rQXLGO8xVRTVmXGbAK7deWuvlO1qgCHVxZswzI1aNyMjQ4ze_9Q", "qi": "nh4sH934RRsFA_T68m0sas6i9NaRYLOYHiK-Z4QUHxpG4lXVM1Q80srDWTYX_bGCa6R2xLTnYkICN4Y3vnUFxbfD4iBRKGdmepegF7jajbBAqCpCHDRTJUisd6MF--VOy-HPB2uIpDRw2X-g01k-AEqy7sXu1YEfh9_jEBf2JXV86mylJEqWJJT4zEtu18pq1ZV157-dLezHt1IZ9VJJldXgj1ZQza8T-15vQFfiwx1vLKZI3YiRlYVPEhCSfSqFh1C6Im9vQ8R_4kymnzDXJirzZZPJKr0FoFlJEUX8mFMCHrhqi0-OSMrCRxci_40Gtd08qo40iWjid0szYeAjfA" }`,
    Scopes:       []string{"openid", "profile", "signicat.national_id"},
}

To get started with OIDC, on must initialize the client. In this example we intialize a client that uses message level encryption with the provider.

ctx := context.Background()
client := oidc.Must(oidc.NewClientMLE(ctx, &config))

To create authorization request one might need to provide some addition options. The example is for Signicat and defines that ftn-op-auth authentication method shall be used and the user interface language is Finnish. The state should be something that is not easy to guess such as UUID.

state := "oneTimeStateHere"
nonce := "oneTimeNonceHere"
options := map[string]string{
    "acr_values": fmt.Sprintf("urn:signicat:oidc:method:ftn-op-auth"),
    "ui_locales": "fi",
    "nonce":      nonce,
}
url, err := client.AuthRequestURL(state, options)
if err != nil {
    log.Fatal("failed to create authorization request url:", err.Error())
}
log.Printf("Authorization URL: %s\n", url)

The next step is to redirect the user to the authorization url. Once the user has completed the authorization process, the service provider will redirect the customer to the redirect_uri that was specified as part of the config.

In the redirect uri handler the request should be validated. Namely, error and error_description query parameters should not be present. In addition, the state should match to the one specified as part of the authorization request. If nonce was specified as part of the authorization request, the id_token should contain the same nonce unmodified. If everything checks out you can proceed to exchanging the authorization code for token. Go-oidc-mle provides a convenience method HandleCallback which validates the request, nonce and state. Alternatively you can do the same manually.

Define a type for the data you're expecting to receive from the user info endpoint of the service provider. The content depends on the scope that you specified. Once again the example below is for the scope that we specified in the config for Signicat.

type User struct {
	Subject    string `json:"sub"`
	Name       string `json:"name"`
	SSN        string `json:"signicat.national_id"`
	GivenName  string `json:"given_name"`
	Locale     string `json:"locale"`
	FamilyName string `json:"family_name"`
}

Exchange the authorization code for access token and request user info.

var userInfo User
err = client.HandleCallback(state, nonce, req.URL.Query(), &userInfo)
if err != nil {
    log.Fatalln("unable to handle callback:", err.Error())
}

response := struct {
    UserInfo User
}{userInfo}

data, err := json.MarshalIndent(response, "", "  ")
if err != nil {
    log.Fatal("unable to marshal user info:", err.Error())
}
log.Println(string(data))

Congratulation, you've now successfully received the user information you requested.

Documentation

Documentation for the libraries go-oidc-mle uses.

Contributing

All contributions are warmly welcome but please write tests for the changes. The existing tests have been written using Ginkgo and Gomega.

To run the tests:

go test -race -coverprofile=coverage.out ./...

To check the coverity on browser:

go tool cover -html=coverage.out

One can also use Ginkgo to run the tests.

ginkgo -cover

Status

Actions Status

Actions Status

Documentation

Overview

Package oidc provides OpenID Connect wrapper that implements message level encryption using the optional encrypted request object.

Index

Constants

View Source
const EncrypterContextKey string = "EncrypterContextKey"

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	ClientId     string
	ClientSecret string
	Endpoint     string
	RedirectUri  string
	LocalJWK     string
	Scopes       []string
}

Config represents the configuration parameters needed for initialising OIDCClient or OIDClientEncrypted.

ClientId is the client id received from the OIDC provider. ClientSecret is the client secret received from the OIDC provider. Endpoint is the OIDC provider's service endpoint. RedirectUri is the redirect uri the provider will redirect the user after authorization. LocalJWK is the JWK we'll use to encrypt the authorization request. It's not needed if MLE is not used. The public must be sent to OIDC provider. Scopes is a list of values that specify which access privileges are being requested from the access token. Must include openid!

type OIDCClient

type OIDCClient struct {
	// contains filtered or unexported fields
}

OIDCClient wraps oauth2 and go-oidc libraries and provides convenience functions for implementing OIDC authorization code flow.

func NewClient

func NewClient(ctx context.Context, config *Config) (*OIDCClient, error)

NewClient initializes and returns OIDClient that can be used to implement OIDC authorization code flow. Discovery is used to read the provider's oidc configuration.

func (*OIDCClient) AuthRequestURL

func (o *OIDCClient) AuthRequestURL(state string, options map[string]interface{}) (string, error)

AuthRequestURL returns the URL for authorization request.

func (*OIDCClient) Exchange

func (o *OIDCClient) Exchange(code string, options map[string]string) (*Tokens, error)

Exchange exchanges the authorization code to a token.

func (*OIDCClient) ExchangeWithNonce

func (o *OIDCClient) ExchangeWithNonce(code, nonce string, options map[string]string) (*Tokens, error)

ExchangeWithNonce exchanges the authorization code to a token and verifies the nonce

func (*OIDCClient) HandleCallback

func (o *OIDCClient) HandleCallback(state, nonce string, queryParams url.Values, user interface{}) error

HandleCallback is a convenience function which exchanges the authorization code to token and then uses the token to request user information from user info endpoint. The implementation does not use message level encryption.

func (*OIDCClient) UserInfo

func (o *OIDCClient) UserInfo(token oauth2.TokenSource, user interface{}) error

UserInfo fetches user information from provider's user info endpoint.

func (*OIDCClient) Verify

func (o *OIDCClient) Verify(token string) (*oidc.IDToken, error)

Verify verifies the signature of the token. Note that the possible nonce in

type OIDCClientEncrypted

type OIDCClientEncrypted struct {
	// contains filtered or unexported fields
}

OIDCClientEncrypted wraps oauth2 and oidc libraries and provides convenience functions for implementing OIDC authentication. The difference between OIDCClientEncrypted and OIDCClient is that the former uses message level encryption when communicating with the service provider.

func NewClientMLE

func NewClientMLE(ctx context.Context, config *Config) (*OIDCClientEncrypted, error)

NewClientMLE initialises and returns OIDClientEncrypted which can be used to implement OIDC authorization code flow with message level encryption. Discovery is used to read the provider's oidc configuration.

func (*OIDCClientEncrypted) AuthRequestURL

func (o *OIDCClientEncrypted) AuthRequestURL(state string, opts map[string]interface{}) (string, error)

AuthRequestURL returns the authorization request URL with encrypted request object.

func (*OIDCClientEncrypted) Exchange

func (o *OIDCClientEncrypted) Exchange(code string, options map[string]string) (*Tokens, error)

Exchange exchanges the authorization code to a token.

func (*OIDCClientEncrypted) ExchangeWithNonce

func (o *OIDCClientEncrypted) ExchangeWithNonce(code, nonce string, options map[string]string) (*Tokens, error)

ExchangeWithNonce Exchanges the authorization code to a token and verifies nonce

func (*OIDCClientEncrypted) HandleCallback

func (o *OIDCClientEncrypted) HandleCallback(state, nonce string, queryParams url.Values, user interface{}) error

HandleCallback is a convenience function which exchanges the authorization code to token and then uses the token to request user information from user info endpoint. The implementation uses message level encryption.

func (*OIDCClientEncrypted) UserInfo

func (o *OIDCClientEncrypted) UserInfo(tokenSource oauth2.TokenSource, destination interface{}) error

UserInfo fetches user information from provider's user info endpoint.

func (*OIDCClientEncrypted) Verify

func (o *OIDCClientEncrypted) Verify(token string) (*oidc.IDToken, error)

Verify verifies the signature of a token.

type OIDCInterface

type OIDCInterface interface {
	Exchange(string, map[string]string) (*Tokens, error)
	ExchangeWithNonce(string, string, map[string]string) (*Tokens, error)
	AuthRequestURL(string, map[string]interface{}) (string, error)
	Verify(string) (*oidc.IDToken, error)
	UserInfo(oauth2.TokenSource, interface{}) error
	HandleCallback(string, string, url.Values, interface{}) error
}

OIDCInterface defines the functions that the clients must implement.

func Must

func Must(client OIDCInterface, err error) OIDCInterface

Must is a convenience function to make sure that the OIDC client is successfully initialised. If the client initialization fails the function panics.

type RemoteKeyStore

type RemoteKeyStore struct {
	jose.JSONWebKeySet
	Context context.Context
	JwksURI string
	Expiry  time.Time
	// contains filtered or unexported fields
}

RemoteKeyStore Stores OIDC provider's JWKs and caches them for the duration specified in the cache-control header. Keys will be refreshed upon expiry.

func (*RemoteKeyStore) ById

func (r *RemoteKeyStore) ById(kid string) (*jose.JSONWebKey, error)

ById returns a key from RemoteKeyStore by key id. If the RemoteKeyStore contains multiple keys with same id then first matching key is returned.

func (*RemoteKeyStore) ByUse

func (r *RemoteKeyStore) ByUse(use string) (*jose.JSONWebKey, error)

ByUse returns a key from RemoteKeyStore by use. If the keystore contains multiple keys with same use then first key will be returned.

type Tokens

type Tokens struct {
	Oauth2Token *oauth2.Token
	IdToken     *oidc.IDToken
}

Tokens combines both the oauth2 token and the oidc specific idToken

Directories

Path Synopsis
examples
mle

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL