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
|
||||
|
||||
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
|
||||
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/go.mod h1:WGI8PadOp+4LjUuI+wNlSwcJwFtY8L9XuNjuO3213HA=
|
||||
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=
|
||||
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/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"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.rootprojects.org/root/keypairs/keyfetch"
|
||||
"git.rootprojects.org/root/libauth"
|
||||
"git.rootprojects.org/root/libauth/chiauth"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
godotenv.Load(".env")
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Unauthenticated Routes
|
||||
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) {
|
||||
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) {
|
||||
tokenVerifier := chiauth.NewTokenVerifier(chiauth.VerificationParams{
|
||||
Issuers: whitelist,
|
||||
Optional: true,
|
||||
Optional: false,
|
||||
})
|
||||
r.Use(tokenVerifier)
|
||||
|
||||
|
|
73
libauth.go
73
libauth.go
|
@ -3,12 +3,16 @@ package libauth
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
"git.rootprojects.org/root/keypairs/keyfetch"
|
||||
)
|
||||
|
||||
const oidcIssuersEnv = "OIDC_ISSUERS"
|
||||
const oidcIssuersInternalEnv = "OIDC_ISSUERS_INTERNAL"
|
||||
|
||||
// JWS is keypairs.JWS with added debugging information
|
||||
type JWS struct {
|
||||
keypairs.JWS
|
||||
|
@ -17,26 +21,69 @@ type JWS struct {
|
|||
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.
|
||||
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)
|
||||
if nil == jws {
|
||||
return nil, fmt.Errorf("Bad Request: malformed Authorization header")
|
||||
}
|
||||
|
||||
if err := jws.DecodeComponents(); nil != err {
|
||||
return &JWS{
|
||||
myJws := &JWS{
|
||||
*jws,
|
||||
false,
|
||||
[]error{err},
|
||||
}, err
|
||||
[]error{},
|
||||
}
|
||||
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.
|
||||
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
|
||||
kid, kidOK := jws.Header["kid"].(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")
|
||||
}
|
||||
|
||||
errs := keypairs.VerifyClaims(pub, jws)
|
||||
errs := keypairs.VerifyClaims(pub, &jws.JWS)
|
||||
if 0 != len(errs) {
|
||||
strs := []string{}
|
||||
for _, err := range errs {
|
||||
jws.Errors = append(jws.Errors, err)
|
||||
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: *jws,
|
||||
Trusted: true,
|
||||
Errors: nil,
|
||||
}, nil
|
||||
jws.Trusted = true
|
||||
return jws, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue