endpoint refresh token keycloak

This commit is contained in:
renaldybrada
2026-02-19 14:13:25 +07:00
parent af80d8c7f6
commit 2ca3cf3564
11 changed files with 233 additions and 12 deletions
+1
View File
@@ -35,5 +35,6 @@ DB_ANTRIAN_SSLMODE=disable
KEYCLOAK_BASE_URL=https://keycloak.example.com
KEYCLOAK_REALM=myrealm
KEYCLOAK_AUDIENCE=my-client-id
KEYCLOAK_SECRET_KEY=super-secret
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/myrealm
KEYCLOAK_IS_ENABLE=false
+32 -3
View File
@@ -504,6 +504,37 @@ const docTemplate = `{
}
}
},
"/keycloak/refresh-token": {
"post": {
"tags": [
"Keycloak"
],
"summary": "Requesting new token to keycloak using refresh token",
"parameters": [
{
"type": "string",
"description": "Valid Refresh Token",
"name": "refresh_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/shared.BaseResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/shared.BaseErrorResponse"
}
}
}
}
},
"/reference/diagnosa/": {
"get": {
"tags": [
@@ -875,8 +906,7 @@ const docTemplate = `{
"jenisKelamin",
"namaPasien",
"noKtp",
"noRekamMedis",
"nomorTelepon"
"noRekamMedis"
],
"properties": {
"alamat": {
@@ -900,7 +930,6 @@ const docTemplate = `{
},
"nomorTelepon": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
+32 -3
View File
@@ -498,6 +498,37 @@
}
}
},
"/keycloak/refresh-token": {
"post": {
"tags": [
"Keycloak"
],
"summary": "Requesting new token to keycloak using refresh token",
"parameters": [
{
"type": "string",
"description": "Valid Refresh Token",
"name": "refresh_token",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/shared.BaseResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/shared.BaseErrorResponse"
}
}
}
}
},
"/reference/diagnosa/": {
"get": {
"tags": [
@@ -869,8 +900,7 @@
"jenisKelamin",
"namaPasien",
"noKtp",
"noRekamMedis",
"nomorTelepon"
"noRekamMedis"
],
"properties": {
"alamat": {
@@ -894,7 +924,6 @@
},
"nomorTelepon": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
+20 -2
View File
@@ -94,7 +94,6 @@ definitions:
nomorTelepon:
items:
type: string
minItems: 1
type: array
tanggalLahir:
type: string
@@ -105,7 +104,6 @@ definitions:
- namaPasien
- noKtp
- noRekamMedis
- nomorTelepon
type: object
antrianoperasi.PasienOperasi:
properties:
@@ -747,6 +745,26 @@ paths:
summary: Get Table Antrian per Sub Spesialis
tags:
- Dashboard
/keycloak/refresh-token:
post:
parameters:
- description: Valid Refresh Token
in: query
name: refresh_token
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/shared.BaseResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/shared.BaseErrorResponse'
summary: Requesting new token to keycloak using refresh token
tags:
- Keycloak
/reference/diagnosa/:
get:
parameters:
+1
View File
@@ -28,6 +28,7 @@ func LoadConfig() *Config {
Realm: getEnv("KEYCLOAK_REALM", "sandbox"),
Audience: getEnv("KEYCLOAK_AUDIENCE", "akbar-test"),
Issuer: getEnv("KEYCLOAK_ISSUER", "https://auth.rssa.top/realms/sandbox"),
SecretKey: getEnv("KEYCLOAK_SECRET_KEY", ""),
IsEnabled: getEnvAsBool("KEYCLOAK_IS_ENABLE", false),
},
}
+1
View File
@@ -55,5 +55,6 @@ type KeycloakConfig struct {
Realm string
Audience string
Issuer string
SecretKey string
IsEnabled bool
}
+47
View File
@@ -0,0 +1,47 @@
package keycloak
import (
"net/http"
baseResponse "antrian-operasi/internal/shared"
"github.com/gin-gonic/gin"
)
type KeycloakHandler struct {
service IKeycloakService
}
func NewKeycloakHandler(service IKeycloakService) KeycloakHandler {
return KeycloakHandler{service}
}
// GetTokenByRefreshCode godoc
// @Summary Requesting new token to keycloak using refresh token
// @Tags Keycloak
// @Param refresh_token query string true "Valid Refresh Token"
// @Success 200 {object} shared.BaseResponse
// @Failure 500 {object} shared.BaseErrorResponse
// @Router /keycloak/refresh-token [post]
func (h KeycloakHandler) GetTokenByRefreshCode(c *gin.Context) {
refreshToken := c.Query("refresh_token")
resp, err := h.service.FetchTokenUsingRefreshToken(c, refreshToken)
if err != nil {
errorResponse := baseResponse.BaseErrorResponse{
Success: false,
Code: 400,
Message: err.Error(),
}
c.JSON(http.StatusInternalServerError, errorResponse)
return
}
response := baseResponse.ToBaseResponse(
resp,
true,
200,
"success refreshing token")
c.JSON(http.StatusOK, response)
}
@@ -0,0 +1,15 @@
package keycloak
type TokenErrorResponse struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type TokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
RefreshExpiresIn int `json:"refresh_expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
}
+14
View File
@@ -0,0 +1,14 @@
package keycloak
import (
"antrian-operasi/internal/config"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(r *gin.RouterGroup, cfg config.KeycloakConfig) {
keycloakService := NewKeycloakService(cfg)
keycloakHandler := NewKeycloakHandler(keycloakService)
r.POST("/refresh-token", keycloakHandler.GetTokenByRefreshCode)
}
+61
View File
@@ -0,0 +1,61 @@
package keycloak
import (
"antrian-operasi/internal/config"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strings"
)
type IKeycloakService interface {
FetchTokenUsingRefreshToken(c context.Context, refreshToken string) (*TokenResponse, error)
}
type KeycloakService struct {
config config.KeycloakConfig
}
func NewKeycloakService(cfg config.KeycloakConfig) IKeycloakService {
return KeycloakService{cfg}
}
func (s KeycloakService) FetchTokenUsingRefreshToken(c context.Context, refreshToken string) (*TokenResponse, error) {
refreshTokenUrl := s.config.BaseUrl + "/realms/" + s.config.Realm + "/protocol/openid-connect/token"
bodyRequest := url.Values{}
bodyRequest.Set("grant_type", "refresh_token")
bodyRequest.Set("client_id", s.config.Audience)
bodyRequest.Set("client_secret", s.config.SecretKey)
bodyRequest.Set("refresh_token", refreshToken)
client := &http.Client{}
req, err := http.NewRequestWithContext(c, http.MethodPost, refreshTokenUrl, strings.NewReader(bodyRequest.Encode()))
if err != nil {
log.Printf("error request token %s", err)
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
log.Printf("error response %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var errResp TokenErrorResponse
json.NewDecoder(resp.Body).Decode(&errResp)
return nil, fmt.Errorf("Keycloak error : %s - %s", errResp.Error, errResp.ErrorDescription)
}
var tokenResp TokenResponse
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
if err != nil {
return nil, err
}
return &tokenResp, nil
}
+9 -4
View File
@@ -5,6 +5,7 @@ import (
"antrian-operasi/internal/database"
antrianoperasi "antrian-operasi/internal/domain/antrian_operasi"
"antrian-operasi/internal/domain/dashboard"
"antrian-operasi/internal/domain/keycloak"
"antrian-operasi/internal/domain/reference/diagnosa"
"antrian-operasi/internal/domain/reference/dokter"
"antrian-operasi/internal/domain/reference/kategori"
@@ -40,13 +41,13 @@ func RegisterRoutes(cfg *config.Config, dbService database.Service) *gin.Engine
log.Fatalf("Unable to initiate keycloak auth")
}
api := router.Group("/api", authKeycloak)
api := router.Group("/api")
antrian := api.Group("/antrian-operasi")
antrian := api.Group("/antrian-operasi", authKeycloak)
{
antrianoperasi.RegisterRoutes(antrian, dbService)
}
reference := api.Group("/reference")
reference := api.Group("/reference", authKeycloak)
{
kategori.RegisterRoutes(reference, dbService)
spesialis.RegisterRoutes(reference, dbService)
@@ -55,10 +56,14 @@ func RegisterRoutes(cfg *config.Config, dbService database.Service) *gin.Engine
diagnosa.RegisterRoutes(reference, dbService)
tindakan.RegisterRoutes(reference, dbService)
}
dboard := api.Group("dashboard")
dboard := api.Group("dashboard", authKeycloak)
{
dashboard.RegisterRoutes(dboard, dbService)
}
kc := api.Group("/keycloak")
{
keycloak.RegisterRoutes(kc, cfg.Keycloak)
}
return router
}