package keycloak
import (
"context"
"github.com/Nerzal/gocloak/v8"
"github.com/dgrijalva/jwt-go/v4"
"log"
"os"
)
type KeycloakInstanceData struct {
ClientPtr gocloak.GoCloak
ServerEndpoint string
User string
Password string
RealmName string
ClientName string
}
var kcloak KeycloakInstanceData
// Initizalize keycloak instance
func Initialize() {
kcloak.ServerEndpoint = os.Getenv("KCLOAK_ENDPOINT")
kcloak.User = os.Getenv("KCLOAK_REALM_USER")
kcloak.Password = os.Getenv("KCLOAK_REALM_PASSWORD")
kcloak.RealmName = os.Getenv("KCLOAK_REALM_NAME")
kcloak.ClientName = os.Getenv("KCLOAK_CLIENT_NAME")
kcloak.ClientPtr = gocloak.NewClient(kcloak.ServerEndpoint)
}
// Check if 'userToken' is valid.
func CheckToken(userToken string) (bool, error) {
_, err := kcloak.ClientPtr.GetUserInfo(context.Background(), userToken, kcloak.RealmName)
if err != nil {
log.Println(err)
return false, err
} else {
return true, err
}
}
// Getter for admin token.
func GetAdminToken() (*gocloak.JWT, error) {
ctx := context.Background()
token, err := kcloak.ClientPtr.LoginAdmin(ctx, kcloak.User, kcloak.Password, kcloak.RealmName)
if err != nil {
log.Println(err)
}
return token, err
}
// Getter for keycloak client instance pointer.
func GetClient() gocloak.GoCloak {
if kcloak.ClientPtr != nil {
Initialize()
}
return kcloak.ClientPtr
}
// Gathers user info from keycloak server using user provided token (userToken parameter)
func GetUserInfo(userToken string) (*gocloak.UserInfo, error) {
ctx := context.Background()
userInfo, err := kcloak.ClientPtr.GetUserInfo(ctx, userToken, kcloak.RealmName)
if err != nil {
log.Println(err)
}
return userInfo, err
}
// Gathers user Roles from keycloak server using 'UserInfo'
func GetUserRoles(userInfo *gocloak.UserInfo) ([]string, error) {
adminToken, err := GetAdminToken()
if err != nil {
log.Println(err)
return nil, err
}
roleMap, err := kcloak.ClientPtr.GetRoleMappingByUserID(context.Background(), adminToken.AccessToken, kcloak.RealmName, *userInfo.Sub)
if err != nil {
log.Println(err)
return nil, err
}
rolesMapping := roleMap.RealmMappings
var roles []string
for _, element := range *rolesMapping {
roles = append(roles, *element.Name)
}
return roles, nil
}
// Parse claims (info) from jwt body into a map. Insecure method, doesn't chek if token is valid.
func GetInsecureTokenClaimMap(tokenString string, clientSecret string) (jwt.MapClaims, error) {
claims := jwt.MapClaims{}
_, err := jwt.ParseWithClaims(tokenString, claims,
func(token *jwt.Token) (interface{}, error) {
return []byte(clientSecret), nil
})
if err != nil {
log.Println(err)
return nil, err
}
return claims, nil
}
func GetInsecureInsecureTokenClaimMap(tokenString string) (jwt.MapClaims, error) {
claims := jwt.MapClaims{}
_, err := jwt.ParseWithClaims(tokenString, claims, nil)
return claims, err
}Usage example:
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"status": http.StatusUnauthorized, "message": common.APINoTokenProvided})
return
}
// Gets token from 'Authorization' header. Splits the Token from 'Bearer' keyword.
accessTokenHeader := strings.Split(c.GetHeader("Authorization"), " ")
if !(len(accessTokenHeader) == 2) {
c.JSON(http.StatusUnauthorized, gin.H{"status": http.StatusUnauthorized, "message": common.APINoTokenProvided})
return
}
// The 'authorization' header value includes the 'bearer' string with a whitespace before the 'Token' string.
accessToken := accessTokenHeader[1]
// Verify if the token is valid
isTokenValid, err := keycloak.CheckToken(accessToken)
if !isTokenValid {
c.JSON(http.StatusUnauthorized, gin.H{"status": http.StatusUnauthorized, "message": err})
return
}