goidc

package module
v0.0.0-...-049ef2a Latest Latest
Warning

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

Go to latest
Published: Mar 22, 2023 License: MIT Imports: 20 Imported by: 0

README

goidc

Golang OpenID Connect Provider Framework (NOT STABLE YET)

wercker status

TokenEndpoint

example:

package main

import (
    "github.com/lyokato/goidc"
    "github.com/lyokato/goidc/grant"
)

func main() {
    realm := "api.example.org"
    endpoint := goidc.NewTokenEndpoint(realm)
    endpoint.Support(grant.AuthorizationCode())
    endpoint.Support(grant.RefreshToken())

    di := my_data_interface.New()

    http.HandlerFunc("/token", endpoint.Handler(di))
    http.ListenAndServe(":8080", nil)
}
  1. Prepare TokenEndpoint with NewTokenEndpoint method
  2. Specify GrantHandler for which grant_type you want to support.
  3. Prepare DataInterface which is bridge to access to data stored in your service.
  4. Finally, call Handler, passing the DataInterface you prepared.

goidc's TokenEndpoint provides you a golang's http.HandlerFunc with it's Handler method. So, it's easy to combine with your favorite Web Application Framework.

For instance, if you'd like to use gin

g := gin.Default()
g.POST("/token", gin.WrapF(endpoint.Handler(di)))

DataInterface

ProtectedResource Endpoint

goidc provids ResourceProtector which supports to check AccessToken validity on your API endpoint.

it has Validate method

Build a middleware according to your favorite Web Application Framework, and in which, call ResourceProtector's Validate method, like this.

package main

import (
    "github.com/lyokato/goidc"
    "github.com/lyokato/goidc/grant"
    "github.com/gin-gonic/gin"
)

func main() {
    realm := "api.example.org"
    rp := goidc.NewResourceProtector(realm)

    di := my_data_interface.New()

    r := gin.Default()  

    r.Use(func(c *gin.Context){

        if rp.Validate(c.Writer, c.Request, di) {
            c.Next()
        } else {
            c.Abort()
        }
    })

    r.GET("/userinfo", my_controller.GetUserInfo)
    r.Run()
}

my_controller package example

package my_controller

func GetUserInfo(c *gin.Context) {

  client_id := c.Request.Header("X-OAUTH-CLIENT-ID")
  scopes := c.Request.Header("X-OAUTH-SCOPE")

  uid_string := c.Request.Header("X-OAUTH-USER-ID")
  user_id, err := strconv.Atoi(uid_string)

  ...
}

So, it means, you can use ResourceProtector to check access token, and if validation is succeeded, ResourceProtector put three values into HTTP header, and pass the request to handler matched to request url's path.

There are three headers

  • X-OAUTH-USER-ID: user_id associated with the access_token
  • X-OAUTH-CLIENT-ID: client_id associated with the access_token
  • X-OAUTH-SCOPE: scope value associated with the access_token

And if token is invalid, ResourceProtector automatically returns error to client in proper manner.

SCOPE VALIDATION

However, in this flow, ResourceProtector put the scope into header, but doesn't confirm this scope matches the condition to access associated API endpoint(path). You need to check it in your handler.

package my_controller

func GetUserInfo(c *gin.Context) {

  client_id := c.Request.Header("X-OAUTH-CLIENT-ID")
  scopes := c.Request.Header("X-OAUTH-SCOPE")

  if !scopeIncludes(scopes, "profile") {
      // return error 
  }

  uid_string := c.Request.Header("X-OAUTH-USER-ID")
  user_id, err := strconv.Atoi(uid_string)

  ...
}

If you hope farther automated validation, There is ValidateWithScopes methods.

pathScopeMap := map[string][]string {
  "/userinfo", []string{"profile", "email"},
  "/picture", []string{"picture"},
}

if rp.ValidateWithScopes(c.Writer, c.Request, di, pathScopeMap) {
    c.Next()
} else {
    c.Abort()
}

JWK Endpoint

You can provide your public keys as JWK for ID Token signature easily with this feature.

For example, Google provides their JWK Here (https://www.googleapis.com/oauth2/v3/certs)

package main

import (
    "github.com/lyokato/goidc"
    "github.com/lyokato/goidc/grant"
)

func main() {
    je := goidc.NewJWKEndpoint()

    // Add Text PEM
    je.AddFromText("my_key_id_1", `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzFyUUfVGyMCbG7YIwgo4XdqEj
hhgIZJ4Kr7VKwIc7F+x0DoBniO6uhU6HVxMPibxSDIGQIHoxP9HJPGF1XlEt7EMw
ewb5Rcku33r+2QCETRmQMw68eZUZqdtgy1JFCFsFUcMwcVcfTqXU00UEevH9RFBH
oqxJsRC0l1ybcs6o0QIDAQAB
-----END PUBLIC KEY-----`)

    // or path of PEM file
    je.AddFromFile("my_pub_key_2", pemFilePath)

    http.HandlerFunc("/cert", je.Handler())
    http.ListenAndServe(":8080", nil)
}

And it returns response like this.

{
    "keys": [
        {
            "kid": "my_key_id_1",
            "kty": "RSA",
            "e": "AQAB",
            "n": "sxclFH1RsjAmxu2CMIKOF3ahI4YYCGSeCq-1SsCHOxfsdA6AZ4juroVOh1cTD4m8UgyBkCB6MT_RyTxhdV5RLexDMHsG-UXJLt96_tkAhE0ZkDMOvHmVGanbYMtSRQhbBVHDMHFXH06l1NNFBHrx_URQR6KsSbEQtJdcm3LOqNE"
        },
        {
            "kid": "my_key_id_2",
            "kty": "RSA",
            "e": "AQAB",
            "n": "sxclFH1RsjAmxu2CMIKOF3ahI4YYCGSeCq-1SsCHOxfsdA6AZ4juroVOh1cTD4m8UgyBkCB6MT_RyTxhdV5RLexDMHsG-UXJLt96_tkAhE0ZkDMOvHmVGanbYMtSRQhbBVHDMHFXH06l1NNFBHrx_URQR6KsSbEQtJdcm3LOqNE"
        }
    ]
}

AuthorizationEndpoint

goidc also support features for AuthorizationEndpoint. This is also gin example. Prepare AuthorizationEndpoit with goidc.NewAuthorizationEndpoint, And call HandleRequest, CompleteRequest, CancelRequest, with object which implements AuthorizationCallbacks interface

policy := authorization.DefaultPolicy()
policy.MaxMaxAge = 60
policy.MinMaxAge = 60
policy.IdTokenExpiresIn = 3600
policy.ConsentOmissionPeriod = 3600
policy.AuthSessionExpiresIn = 60

di := my_data_interface.New()
ai := goidc.NewAuthorizationEndpoint(di, policy)

r.GET("/authorize", func(c *gin.Context) {
  ai.HandleRequest(c.Writer, c.Request,
    my_authorization_callbaskc.New(c))
})
r.POST("/authorize", func(c *gin.Context) {
  ai.HandleRequest(c.Writer, c.Request,
    my_authorization_callbaskc.New(c))
})
r.POST("/authorized", func(c *gin.Context) {
  ai.CompleteRequest(c.Writer, c.Request,
    my_authorization_callbaskc.New(c))
})

r.POST("/unauthorized", func(c *gin.Context) {
  ai.CancelRequest(c.Writer, c.Request,
    my_authorization_callbaskc.New(c))
})

Documentation

Index

Constants

View Source
const ClientAssertionTypeJWT = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthorizationEndpoint

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

func NewAuthorizationEndpoint

func NewAuthorizationEndpoint(di bridge.DataInterface, policy *authorization.Policy) *AuthorizationEndpoint

func (*AuthorizationEndpoint) CancelRequest

func (*AuthorizationEndpoint) CompleteRequest

func (*AuthorizationEndpoint) HandleRequest

func (*AuthorizationEndpoint) SetLogger

func (a *AuthorizationEndpoint) SetLogger(l log.Logger)

func (*AuthorizationEndpoint) SetTimeBuilder

func (a *AuthorizationEndpoint) SetTimeBuilder(builder io.TimeBuilder)

type CredentialAcceptanceMethod

type CredentialAcceptanceMethod int
const (
	FromHeader CredentialAcceptanceMethod = iota
	FromHeaderAndPostBody
	FromAll
)

type JWKEndpoint

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

func NewJWKEndpoint

func NewJWKEndpoint() *JWKEndpoint

func (*JWKEndpoint) AddFromFile

func (e *JWKEndpoint) AddFromFile(kid, path string)

func (*JWKEndpoint) AddFromText

func (e *JWKEndpoint) AddFromText(kid, pem string)

func (*JWKEndpoint) Handler

func (e *JWKEndpoint) Handler() http.HandlerFunc

type ResourceProtector

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

func NewResourceProtector

func NewResourceProtector(realm string) *ResourceProtector

func (*ResourceProtector) AcceptAccessToken

func (rp *ResourceProtector) AcceptAccessToken(meth CredentialAcceptanceMethod)

func (*ResourceProtector) SetErrorURI

func (rp *ResourceProtector) SetErrorURI(uri string)

func (*ResourceProtector) SetErrorURIBulder

func (rp *ResourceProtector) SetErrorURIBulder(builder oer.OAuthErrorURIBuilder)

func (*ResourceProtector) SetLogger

func (rp *ResourceProtector) SetLogger(l log.Logger)

func (*ResourceProtector) SetTimeBuilder

func (rp *ResourceProtector) SetTimeBuilder(builder io.TimeBuilder)

func (*ResourceProtector) Validate

func (*ResourceProtector) ValidateWithScopes

func (rp *ResourceProtector) ValidateWithScopes(w http.ResponseWriter, r *http.Request,
	sdi bridge.DataInterface, scopeMap map[string][]string) bool

type TokenEndpoint

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

func NewTokenEndpoint

func NewTokenEndpoint(realm string) *TokenEndpoint

func (*TokenEndpoint) AcceptClientAssertion

func (te *TokenEndpoint) AcceptClientAssertion(accept bool)

func (*TokenEndpoint) AcceptClientSecret

func (te *TokenEndpoint) AcceptClientSecret(
	meth CredentialAcceptanceMethod)

func (*TokenEndpoint) Handler

func (*TokenEndpoint) SetErrorURI

func (te *TokenEndpoint) SetErrorURI(uri string)

func (*TokenEndpoint) SetErrorURIBuilder

func (te *TokenEndpoint) SetErrorURIBuilder(builder oer.OAuthErrorURIBuilder)

func (*TokenEndpoint) SetLogger

func (te *TokenEndpoint) SetLogger(l log.Logger)

func (*TokenEndpoint) SetTimeBuilder

func (te *TokenEndpoint) SetTimeBuilder(builder io.TimeBuilder)

func (*TokenEndpoint) Support

func (te *TokenEndpoint) Support(handler *grant.GrantHandler)

Jump to

Keyboard shortcuts

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