It might not be news to you, but this will explain a little bit about Go, making http requests and parsing the result.
OpenID-Connect (oidc) is an identity protocol, you could call it an Oauth2 dialect. It manages your users per realm, well not the protocol but the server does.
Every oidc idp (identity provider aka server) should support the oidc discovery feature.
What is a good OIDC IDP? Keycloak for instance, because it’s free.
Essentially it’s a well known URI that provides information about this IDP or this IDP’s realm in JSON.
The “.well-known/openid-configuration” is appended to the IDP.
To see a live one you could navigate to https://connect.icod.de/auth/realms/testrealm/.well-known/openid-configuration
It lists all the endpoints this server handles and supported grant types and much much more.
I’ve been working with websockets lately and faced the challenge that websockets don’t support passing HTTP headers,
so I had to log in with the token my frontend received by the IDP. And for security reasons this had to be the raw token, not the parsed subject field, because it’s not cryptographically protected.
This means I had to ask the IDP if the token I had received was valid and extract the subject from it.
The below code is the 1st version of how I did it.
It queries the openid-connect discovery document, since the structure was unknown to me, I decoded the response body from the request into a map[string]interface{}
.
However in retrospect, I could’ve defined a struct with only the single requested variable in it:
1 2 3 |
type DiscoveryDocument struct { UserinfoEndpoint string `json:"userinfo_endpoint"` } |
Then this userinfo endpoint is queried with the Accesstoken passed as a Bearer token in the Authorization header.
The result is decoded into the UserInfo struct instance and returned by the function.
I use spew, which is a very helpful tool to display the content of the returned variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package main import ( "encoding/json" "fmt" "log" "net/http" "github.com/davecgh/go-spew/spew" ) type UserInfo struct { Subject string `json:"sub"` Email string `json:"email"` EmailVerified bool `json:"email_verified"` Name string `json:"name"` FamilyName string `json:"family_name"` GivenName string `json:"given_name"` PreferredUsername string `json:"preferred_username"` } func main() { token := "ey...paste your token here" discoveryURL := "https://localhost:8080/auth/realms/testrealm/.well-known/openid-configuration" userInfo := getUserInfo(token, discoveryURL) spew.Dump(userInfo) } func getUserInfo(token, discoveryURL string) *UserInfo { rsp, e := http.Get(discoveryURL) if e != nil { log.Println("getUserInfo: could not connect to oidc idp", e.Error()) return nil } rspbody := make(map[string]interface{}) dec := json.NewDecoder(rsp.Body) if e := dec.Decode(&rspbody); e != nil { log.Println("getUserInfo: decode oidc response body", e.Error()) return nil } uie := rspbody["userinfo_endpoint"].(string) client := http.DefaultClient uireq, e := http.NewRequest(http.MethodGet, uie, nil) if e != nil { log.Println("getUserInfo: NewRequest error", e.Error()) return nil } uireq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) uirsp, e := client.Do(uireq) if e != nil { log.Println("getUserInfo: could not connect to oidc userinfo endpoint", e.Error()) return nil } userinfo := new(UserInfo) uidec := json.NewDecoder(uirsp.Body) if e := uidec.Decode(userinfo); e != nil { log.Println("getUserInfo: could not connect decode userinfo endpoint response body", e.Error()) return nil } return userinfo } |