feat: add more convenience methods
This commit is contained in:
parent
97d0f2eec0
commit
bdad6bf8ed
|
@ -3,7 +3,12 @@ module git.rootprojects.org/root/libauth/examples
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.rootprojects.org/root/keypairs v0.6.5
|
git.rootprojects.org/root/dotenv v1.0.0
|
||||||
git.rootprojects.org/root/libauth v0.1.0
|
git.rootprojects.org/root/libauth v0.1.0
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.rootprojects.org/root/keypairs v0.6.5 // indirect
|
||||||
|
github.com/joho/godotenv v1.3.0 // indirect
|
||||||
|
)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
git.rootprojects.org/root/dotenv v1.0.0 h1:aQIOAghd9DlgicIH+Z0K9HPjGFnMdtRAEoOmnT3A0uw=
|
||||||
|
git.rootprojects.org/root/dotenv v1.0.0/go.mod h1:sA3rt078/Uc8/roUvD4DQZk/BtmrYPGv3o4DTqwnfa0=
|
||||||
git.rootprojects.org/root/keypairs v0.6.5 h1:sdRAQD/O/JBS8+ZxUewXnY+cjQVDNH3TmcS+KtANZqA=
|
git.rootprojects.org/root/keypairs v0.6.5 h1:sdRAQD/O/JBS8+ZxUewXnY+cjQVDNH3TmcS+KtANZqA=
|
||||||
git.rootprojects.org/root/keypairs v0.6.5/go.mod h1:WGI8PadOp+4LjUuI+wNlSwcJwFtY8L9XuNjuO3213HA=
|
git.rootprojects.org/root/keypairs v0.6.5/go.mod h1:WGI8PadOp+4LjUuI+wNlSwcJwFtY8L9XuNjuO3213HA=
|
||||||
git.rootprojects.org/root/libauth v0.1.0 h1:qM73YYBLByoFTJUXH2ZeUhJdLzY35t4jgNoUAyqH2QA=
|
git.rootprojects.org/root/libauth v0.1.0 h1:qM73YYBLByoFTJUXH2ZeUhJdLzY35t4jgNoUAyqH2QA=
|
||||||
git.rootprojects.org/root/libauth v0.1.0/go.mod h1:bbLDWn0w7I1VfOMP2DZU/t/H9Ln0mT61K+ELH4ievVM=
|
git.rootprojects.org/root/libauth v0.1.0/go.mod h1:bbLDWn0w7I1VfOMP2DZU/t/H9Ln0mT61K+ELH4ievVM=
|
||||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
|
|
@ -7,24 +7,39 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"git.rootprojects.org/root/libauth"
|
||||||
|
|
||||||
"git.rootprojects.org/root/keypairs/keyfetch"
|
|
||||||
"git.rootprojects.org/root/libauth/chiauth"
|
"git.rootprojects.org/root/libauth/chiauth"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
godotenv.Load(".env")
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
whitelist, err := keyfetch.NewWhitelist([]string{"https://therootcompany.github.io/libauth/"})
|
if 0 == len(os.Getenv("OIDC_ISSUERS")) {
|
||||||
|
os.Setenv("OIDC_ISSUERS", "https://therootcompany.github.io/libauth/")
|
||||||
|
}
|
||||||
|
whitelist, err := libauth.ParseIssuerEnvs("OIDC_ISSUERS", "OIDC_ISSUERS_INTERNAL")
|
||||||
if nil != err {
|
if nil != err {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unauthenticated Routes
|
// Unauthenticated Routes
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
tokenVerifier := chiauth.NewTokenVerifier(chiauth.VerificationParams{
|
||||||
|
Issuers: whitelist,
|
||||||
|
Optional: true,
|
||||||
|
})
|
||||||
|
r.Use(tokenVerifier)
|
||||||
r.Post("/api/hello", func(w http.ResponseWriter, r *http.Request) {
|
r.Post("/api/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte(`{ "message": "Hello, World!" }`))
|
jws := chiauth.GetJWS(r)
|
||||||
|
|
||||||
|
w.Write([]byte(
|
||||||
|
fmt.Sprintf(`{ "message": "Hello, World!", "authenticated": %t }`, jws.Trusted),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,7 +47,7 @@ func main() {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
tokenVerifier := chiauth.NewTokenVerifier(chiauth.VerificationParams{
|
tokenVerifier := chiauth.NewTokenVerifier(chiauth.VerificationParams{
|
||||||
Issuers: whitelist,
|
Issuers: whitelist,
|
||||||
Optional: true,
|
Optional: false,
|
||||||
})
|
})
|
||||||
r.Use(tokenVerifier)
|
r.Use(tokenVerifier)
|
||||||
|
|
||||||
|
|
77
libauth.go
77
libauth.go
|
@ -3,12 +3,16 @@ package libauth
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.rootprojects.org/root/keypairs"
|
"git.rootprojects.org/root/keypairs"
|
||||||
"git.rootprojects.org/root/keypairs/keyfetch"
|
"git.rootprojects.org/root/keypairs/keyfetch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const oidcIssuersEnv = "OIDC_ISSUERS"
|
||||||
|
const oidcIssuersInternalEnv = "OIDC_ISSUERS_INTERNAL"
|
||||||
|
|
||||||
// JWS is keypairs.JWS with added debugging information
|
// JWS is keypairs.JWS with added debugging information
|
||||||
type JWS struct {
|
type JWS struct {
|
||||||
keypairs.JWS
|
keypairs.JWS
|
||||||
|
@ -17,26 +21,69 @@ type JWS struct {
|
||||||
Errors []error `json:"errors,omitempty"`
|
Errors []error `json:"errors,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IssuerList is the trusted list of token issuers
|
||||||
|
type IssuerList = keyfetch.Whitelist
|
||||||
|
|
||||||
|
// ParseIssuerEnvs will parse ENVs (both comma- and space-delimited) to
|
||||||
|
// create a trusted IssuerList of public and/or internal issuer URLs.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// OIDC_ISSUERS='https://example.com/ https://therootcompany.github.io/libauth/'
|
||||||
|
// OIDC_ISSUERS_INTERNAL='http://localhost:3000/ http://my-service-name:8080/'
|
||||||
|
func ParseIssuerEnvs(issuersEnvName, internalEnvName string) (IssuerList, error) {
|
||||||
|
if len(issuersEnvName) > 0 {
|
||||||
|
issuersEnvName = oidcIssuersEnv
|
||||||
|
}
|
||||||
|
pubs := os.Getenv(issuersEnvName)
|
||||||
|
pubURLs := ParseIssuerListString(pubs)
|
||||||
|
|
||||||
|
if len(internalEnvName) > 0 {
|
||||||
|
internalEnvName = oidcIssuersInternalEnv
|
||||||
|
}
|
||||||
|
internals := os.Getenv(internalEnvName)
|
||||||
|
internalURLs := ParseIssuerListString(internals)
|
||||||
|
|
||||||
|
return keyfetch.NewWhitelist(pubURLs, internalURLs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIssuerListString will Split comma- and/or space-delimited list into a slice
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// "https://example.com/, https://therootcompany.github.io/libauth/"
|
||||||
|
func ParseIssuerListString(issuerList string) []string {
|
||||||
|
issuers := []string{}
|
||||||
|
|
||||||
|
issuerList = strings.TrimSpace(issuerList)
|
||||||
|
if len(issuerList) > 0 {
|
||||||
|
issuerList = strings.ReplaceAll(issuerList, ",", " ")
|
||||||
|
issuers = strings.Fields(issuerList)
|
||||||
|
}
|
||||||
|
|
||||||
|
return issuers
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyJWT will return a verified InspectableToken if possible, or otherwise as much detail as possible, possibly including an InspectableToken with failed verification.
|
// VerifyJWT will return a verified InspectableToken if possible, or otherwise as much detail as possible, possibly including an InspectableToken with failed verification.
|
||||||
func VerifyJWT(jwt string, issuers keyfetch.Whitelist, r *http.Request) (*JWS, error) {
|
func VerifyJWT(jwt string, issuers IssuerList, r *http.Request) (*JWS, error) {
|
||||||
jws := keypairs.JWTToJWS(jwt)
|
jws := keypairs.JWTToJWS(jwt)
|
||||||
if nil == jws {
|
if nil == jws {
|
||||||
return nil, fmt.Errorf("Bad Request: malformed Authorization header")
|
return nil, fmt.Errorf("Bad Request: malformed Authorization header")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := jws.DecodeComponents(); nil != err {
|
myJws := &JWS{
|
||||||
return &JWS{
|
*jws,
|
||||||
*jws,
|
false,
|
||||||
false,
|
[]error{},
|
||||||
[]error{err},
|
}
|
||||||
}, err
|
if err := myJws.DecodeComponents(); nil != err {
|
||||||
|
myJws.Errors = append(myJws.Errors, err)
|
||||||
|
return myJws, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return VerifyJWS(jws, issuers, r)
|
return VerifyJWS(myJws, issuers, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyJWS takes a fully decoded JWS and will return a verified InspectableToken if possible, or otherwise as much detail as possible, possibly including an InspectableToken with failed verification.
|
// VerifyJWS takes a fully decoded JWS and will return a verified InspectableToken if possible, or otherwise as much detail as possible, possibly including an InspectableToken with failed verification.
|
||||||
func VerifyJWS(jws *keypairs.JWS, issuers keyfetch.Whitelist, r *http.Request) (*JWS, error) {
|
func VerifyJWS(jws *JWS, issuers IssuerList, r *http.Request) (*JWS, error) {
|
||||||
var pub keypairs.PublicKey
|
var pub keypairs.PublicKey
|
||||||
kid, kidOK := jws.Header["kid"].(string)
|
kid, kidOK := jws.Header["kid"].(string)
|
||||||
iss, issOK := jws.Claims["iss"].(string)
|
iss, issOK := jws.Claims["iss"].(string)
|
||||||
|
@ -67,18 +114,16 @@ func VerifyJWS(jws *keypairs.JWS, issuers keyfetch.Whitelist, r *http.Request) (
|
||||||
return nil, fmt.Errorf("Bad Request: self-signed tokens with 'jwk' are not supported")
|
return nil, fmt.Errorf("Bad Request: self-signed tokens with 'jwk' are not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := keypairs.VerifyClaims(pub, jws)
|
errs := keypairs.VerifyClaims(pub, &jws.JWS)
|
||||||
if 0 != len(errs) {
|
if 0 != len(errs) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
|
jws.Errors = append(jws.Errors, err)
|
||||||
strs = append(strs, err.Error())
|
strs = append(strs, err.Error())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("invalid jwt:\n%s", strings.Join(strs, "\n\t"))
|
return jws, fmt.Errorf("invalid jwt:\n%s", strings.Join(strs, "\n\t"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &JWS{
|
jws.Trusted = true
|
||||||
JWS: *jws,
|
return jws, nil
|
||||||
Trusted: true,
|
|
||||||
Errors: nil,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue