Gå til indholdet

Kodeeksempler

Eksemplerne dækker de samme 7 scenarier i begge sprog, så du kan læse Python og C# side om side.

# Scenarie Endpoint / handling
1 Hent brugerprofil GET /v1/user
2 Hent autorisationsliste GET /v1/user/authorizations
3 Skift autorisation GET /v1/user + X-VDX-AuthorizationId
4 Slip autorisation GET /v1/user uden header
5 Hent gruppe + parent-træ GET /v1/user/group, GET /v1/user/group/parents
6 Token-refresh Keycloak token_endpoint
7 Fejlhåndtering 401 / 403 fra Auth API

Forudsætninger i eksemplerne

Erstat placeholder-værdier:

Variabel Beskrivelse
ACCESS_TOKEN JWT access token fra Keycloak efter login
AUTH_API_BASE https://mgmtauthapi.vconf-stage.dk (stage)
KEYCLOAK_TOKEN_URL Fra OIDC discovery token_endpoint
CLIENT_ID Din Keycloak-klient
REFRESH_TOKEN Refresh token fra login

Python kræver: pip install requests

1. Hent brugerprofil (GET /v1/user)

Kald straks efter login — uden succesfuldt svar har du ingen gyldig VDX-kontekst.

import requests

AUTH_API_BASE = "https://mgmtauthapi.vconf-stage.dk"
ACCESS_TOKEN = "DIT_ACCESS_TOKEN"  # fra Keycloak efter login

def auth_headers(access_token: str, authorization_id: int | None = None) -> dict:
    headers = {"Authorization": f"Bearer {access_token}"}
    if authorization_id and authorization_id > 0:
        headers["X-VDX-AuthorizationId"] = str(authorization_id)
    return headers

response = requests.get(
    f"{AUTH_API_BASE}/v1/user",
    headers=auth_headers(ACCESS_TOKEN),
    timeout=30,
)

if response.status_code == 200:
    profile = response.json()
    print(f"Velkommen {profile['firstname']} — gruppe {profile['organisationGroup']}")
elif response.status_code in (401, 403):
    raise RuntimeError("Login afvist — profil-kald fejlede")
else:
    response.raise_for_status()
using System.Net.Http.Headers;
using System.Text.Json;

const string AuthApiBase = "https://mgmtauthapi.vconf-stage.dk";
const string AccessToken = "DIT_ACCESS_TOKEN"; // fra Keycloak efter login

static HttpRequestMessage CreateAuthApiRequest(HttpMethod method, string path, int? authorizationId = null)
{
    var request = new HttpRequestMessage(method, $"{AuthApiBase}{path}");
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
    if (authorizationId is > 0)
        request.Headers.TryAddWithoutValidation("X-VDX-AuthorizationId", authorizationId.Value.ToString());
    return request;
}

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };

using var request = CreateAuthApiRequest(HttpMethod.Get, "/v1/user");
using var response = await http.SendAsync(request);

if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
    var json = await response.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(json);
    var profile = doc.RootElement;
    var groupId = profile.GetProperty("organisationGroup").GetInt64();
    Console.WriteLine($"Gruppe: {groupId}");
}
else if (response.StatusCode is System.Net.HttpStatusCode.Unauthorized
         or System.Net.HttpStatusCode.Forbidden)
{
    throw new InvalidOperationException("Login afvist — profil-kald fejlede");
}
else
{
    response.EnsureSuccessStatusCode();
}

2. Hent autorisationsliste (GET /v1/user/authorizations)

response = requests.get(
    f"{AUTH_API_BASE}/v1/user/authorizations",
    headers=auth_headers(ACCESS_TOKEN),
    timeout=30,
)
response.raise_for_status()

authorizations = response.json()
for auth in authorizations:
    print(f"{auth['id']}: {auth['groupName']} ({auth['role']})")
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };

using var request = CreateAuthApiRequest(HttpMethod.Get, "/v1/user/authorizations");
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);

foreach (var auth in doc.RootElement.EnumerateArray())
{
    var id = auth.GetProperty("id").GetInt32();
    var groupName = auth.GetProperty("groupName").GetString();
    var role = auth.GetProperty("role").GetString();
    Console.WriteLine($"{id}: {groupName} ({role})");
}

3. Skift autorisation

Verificér skiftet mod API'et før du persisterer valget i session/cookie.

AUTHORIZATION_ID = 42  # fra autorisationslisten

response = requests.get(
    f"{AUTH_API_BASE}/v1/user",
    headers=auth_headers(ACCESS_TOKEN, authorization_id=AUTHORIZATION_ID),
    timeout=30,
)

if response.status_code == 403:
    print("Autorisation afvist — behold eksisterende kontekst")
elif response.status_code == 200:
    profile = response.json()
    assert profile["isAuthorization"] is True
    # Persister AUTHORIZATION_ID i session/cookie
    print(f"Skiftet til {profile['organisationName']} (gruppe {profile['organisationGroup']})")
const int authorizationId = 42;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var request = CreateAuthApiRequest(HttpMethod.Get, "/v1/user", authorizationId);
using var response = await http.SendAsync(request);

if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
{
    Console.WriteLine("Autorisation afvist — behold eksisterende kontekst");
}
else if (response.IsSuccessStatusCode)
{
    var json = await response.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(json);
    var isAuth = doc.RootElement.GetProperty("isAuthorization").GetBoolean();
    // Persister authorizationId i session/cookie
    Console.WriteLine($"Skiftet — isAuthorization: {isAuth}");
}

4. Slip autorisation

response = requests.get(
    f"{AUTH_API_BASE}/v1/user",
    headers=auth_headers(ACCESS_TOKEN),  # ingen X-VDX-AuthorizationId
    timeout=30,
)
response.raise_for_status()

profile = response.json()
assert profile["isAuthorization"] is False
# Fjern persisteret autorisations-ID fra session/cookie
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var request = CreateAuthApiRequest(HttpMethod.Get, "/v1/user"); // ingen authorizationId
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var isAuth = doc.RootElement.GetProperty("isAuthorization").GetBoolean();
// Fjern persisteret autorisations-ID fra session/cookie

5. Hent gruppe + parent-træ

auth_id = 42  # eller None for basis-kontekst

# Flad gruppe
group_resp = requests.get(
    f"{AUTH_API_BASE}/v1/user/group",
    headers=auth_headers(ACCESS_TOKEN, authorization_id=auth_id),
    timeout=30,
)
group_resp.raise_for_status()
group = group_resp.json()

# Parent-træ
tree_resp = requests.get(
    f"{AUTH_API_BASE}/v1/user/group/parents",
    headers=auth_headers(ACCESS_TOKEN, authorization_id=auth_id),
    timeout=30,
)
tree_resp.raise_for_status()
tree = tree_resp.json()
int? authId = 42; // eller null for basis-kontekst

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };

// Flad gruppe
using (var groupReq = CreateAuthApiRequest(HttpMethod.Get, "/v1/user/group", authId))
using (var groupResp = await http.SendAsync(groupReq))
{
    groupResp.EnsureSuccessStatusCode();
    var groupJson = await groupResp.Content.ReadAsStringAsync();
    // deserialiser til Group DTO efter behov
}

// Parent-træ
using (var treeReq = CreateAuthApiRequest(HttpMethod.Get, "/v1/user/group/parents", authId))
using (var treeResp = await http.SendAsync(treeReq))
{
    treeResp.EnsureSuccessStatusCode();
    var treeJson = await treeResp.Content.ReadAsStringAsync();
}

6. Token-refresh (OIDC mod Keycloak)

KEYCLOAK_TOKEN_URL = "https://login.vconf-stage.dk/auth/realms/broker/protocol/openid-connect/token"
CLIENT_ID = "din-keycloak-klient-id"
REFRESH_TOKEN = "DIT_REFRESH_TOKEN"

response = requests.post(
    KEYCLOAK_TOKEN_URL,
    data={
        "grant_type": "refresh_token",
        "client_id": CLIENT_ID,
        "refresh_token": REFRESH_TOKEN,
    },
    timeout=30,
)

if response.status_code == 200:
    tokens = response.json()
    new_access_token = tokens["access_token"]
    new_refresh_token = tokens.get("refresh_token", REFRESH_TOKEN)
    # Gem nye tokens i session/cookie
else:
    # Refresh fejlede — log brugeren ud
    raise RuntimeError("Session udløbet")
const string KeycloakTokenUrl =
    "https://login.vconf-stage.dk/auth/realms/broker/protocol/openid-connect/token";
const string ClientId = "din-keycloak-klient-id";
const string RefreshToken = "DIT_REFRESH_TOKEN";

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };

var form = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["grant_type"] = "refresh_token",
    ["client_id"] = ClientId,
    ["refresh_token"] = RefreshToken,
});

using var response = await http.PostAsync(KeycloakTokenUrl, form);

if (response.IsSuccessStatusCode)
{
    var json = await response.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(json);
    var newAccessToken = doc.RootElement.GetProperty("access_token").GetString();
    // Gem nye tokens — opdater også refresh_token hvis Keycloak roterer
}
else
{
    throw new InvalidOperationException("Session udløbet — log brugeren ud");
}

7. Fejlhåndtering (401/403)

Implementér fail-closed når profil-kald fejler efter login.

def get_user_profile_or_fail(access_token: str) -> dict:
    response = requests.get(
        f"{AUTH_API_BASE}/v1/user",
        headers=auth_headers(access_token),
        timeout=30,
    )

    if response.status_code == 401:
        # Forsøg token-refresh, ellers logud
        raise PermissionError("Token ugyldigt eller udløbet")
    if response.status_code == 403:
        # Deaktiveret bruger eller ugyldig autorisation
        raise PermissionError("Adgang nægtet — log brugeren ud")
    if response.status_code >= 500:
        raise ConnectionError("Auth API utilgængeligt")

    response.raise_for_status()
    return response.json()
async Task<JsonDocument> GetUserProfileOrFailAsync(HttpClient http, string accessToken)
{
    using var request = new HttpRequestMessage(HttpMethod.Get, $"{AuthApiBase}/v1/user");
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

    using var response = await http.SendAsync(request);

    return response.StatusCode switch
    {
        System.Net.HttpStatusCode.OK =>
            JsonDocument.Parse(await response.Content.ReadAsStringAsync()),
        System.Net.HttpStatusCode.Unauthorized =>
            throw new UnauthorizedAccessException("Token ugyldigt eller udløbet"),
        System.Net.HttpStatusCode.Forbidden =>
            throw new UnauthorizedAccessException("Adgang nægtet — log brugeren ud"),
        >= System.Net.HttpStatusCode.InternalServerError =>
            throw new HttpRequestException("Auth API utilgængeligt"),
        _ => throw new HttpRequestException($"Uventet status: {response.StatusCode}"),
    };
}

Se også