feat: add more convenience methods

This commit is contained in:
AJ ONeal 2022-05-09 17:08:38 -06:00
parent 97d0f2eec0
commit bdad6bf8ed
No known key found for this signature in database
GPG Key ID: 562702827EF68D87
4 changed files with 92 additions and 23 deletions

View File

@ -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
)

View File

@ -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=

View File

@ -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)

View File

@ -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{err}, []error{},
}, 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
} }