Browse Source

feat: add more convenience methods

main v0.1.3
AJ ONeal 2 years ago
parent
commit
bdad6bf8ed
No known key found for this signature in database GPG Key ID: 562702827EF68D87
  1. 7
      examples/go.mod
  2. 4
      examples/go.sum
  3. 27
      examples/server.go
  4. 77
      libauth.go

7
examples/go.mod

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

4
examples/go.sum

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

27
examples/server.go

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

77
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{
*jws,
false,
[]error{err},
}, err
myJws := &JWS{
*jws,
false,
[]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…
Cancel
Save