Projekte

'Sign in with Intastellar' - authenticator

Web development

Tech: JS, MySQL, PHP

Jeg har udviklet en egen authentication løsning, som kan implementeres på forskellige domæner. Løsningen er udviklet i PHP og MySQL.

'Sign in with Intastellar' - authenticator

Beskrivelse

Med Intastellar Signin authentication har jeg udviklet en auth service for en af mine hjemmesider, intastellaracconts.com som holder styr af alt hvad authentication angår. Men hvorfor har jeg valgt at udvikle min egen authentication service, når der findes mange andre derude?

Det startede tilbage i 2018 da jeg havde 2-3 forskellige hjemmesider, og hvor jeg meget gerne vil bruge en authentication løsning samt 1 konto till alle domæner. Derfor begyndte jeg at udvikle min egen authentication service som i første omgang var simple og kun kunne bruges på en hjemmeside. Dog hurtigt var jeg interessert i hvordan "Sign in with Google" fungerede og hvordan jeg kunne bruge det samme princip for mig selv.

Jeg fandt ret hurtigt ud af at de benyttede sig postMessage metoden for at dele informationer cross-domain. Derfor begyndte jeg hurtigt i, at sætte mig ind i hvordan det fungerede og hvordan jeg kan benytte det.

PostMessage

PostMessage er en metode som gør det muligt at sende beskeder mellem vinduer/frames på tværs af domæner. Metoden er en del af HTML5 og er en sikker metode for at kommunikere mellem forskellige domæner.

Metoden er bygget op af 2 parametre, en besked og en targetOrigin. Beskeden er den data som du gerne vil sende, og targetOrigin er den URL som du gerne vil sende beskeden til.

JWT

Når en bruger logger sig ind på intastellaraccounts.com, med deres email og passkey, og han bliver verificeret uden problemer, bliver der generet en JWT.

JWT består af en header som indholder hvem er udstederen, med både navn og url. I body indholder den 2 informationer fra brugeren som er logget ind

  1. Brugerens ID
  2. Brugerens email
  3. Scope
Denne token bliver sendt ned til parent vinduet, hvor den bliver gemt og benyttet af scriptet for at sende den vider til verificering, inden hjemmesiden for adgang til brugerens data.

Genereing af JWT Token (Server-Side)

$publichash = array(
	"issuer" => "Intastellar Account",
	"issuer_url" => "https://apis.intastellaraccounts.com",
	"exp" => time() + 83400 * 360,
);

$data = array(
	"user_id" => $newsalt['salt'],
	"email" => $newsalt["email"],
	"allowed_scope" => $_GET["scope"]
);

$headers = array(
	"iss" => "Intastellar Account",
	"iss_domain" => "apis.intastellaraccounts.com",
	"exp" => time() + 83400 * 360,
);

$headers = json_encode($headers);
$data = json_encode($data);

$signing_key = "{en hemmelig nøgle}";
$signiture = hash_hmac("sha256", base64_encode($headers) . "." . base64_encode($data), $signing_key, true);
$token = base64_encode($headers) . "." . base64_encode($data) . "." . base64_encode($signiture);

$jwt = base64_encode($token);

Send token til parent (Client-Side)

window.addEventListener("DOMContentLoaded", function() {
    const JWT = "[Genereret JWT]";
    window.opener.postMessage(JWT, "*");
    window.addEventListener("message", function(e) {
        if (e.data == "iframe-token-recieved") {
            window.parent.close();
        }
    })
});

Verifikation

Token bliver sendt via Authentication header til verificerings server, hvor den bliver decoded og verificeret.

Under verificering bliver der checket om token er for gamlet (maks 30 min.) og om issueren er den korrekte samt om signaturen er korrekt.

Client-Side Verificering & udførelsen af bruger defineret funktion fra data- attributten.

fetch("https://apis.intastellaraccounts.com/verify", {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + token
    }
}).then(e => e.json()).then(e => {
    if (e.statusCode == 200) {
        const { phone, birthday } = e.account.user[0];
        e.account.user.phone = phone;
        e.account.user.birthday = birthday;
        delete e.account.user[0];
        fn(e.account);
    } else {
        throw new IntastellarSolutionsSDKError(e.error);
    }
})

Client-Side Verificering & vidersendelse til hjemmesiden med brugerens information

fetch("https://apis.intastellaraccounts.com/verify", {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + token
    }
}).then(e => e.json()).then(e => {
    if (e.statusCode == 200) {
        const { phone, birthday } = e.account.user[0];
        e.account.user.phone = phone;
        e.account.user.birthday = birthday;
        delete e.account.user[0];
        throw new IntastellarSolutionsSDKSuccess("We´ve successfully send user data for: " + t.name);
        if (window.location.href.indexOf("?") > -1) {
            const query = "?" + window.location.href.split("?")[1];
            // Add the query string to the url
            window.location.href = window.location.protocol + "//" + document.querySelector("[data-login_uri]").getAttribute("data-login_uri") + query + "&token=" + JSON.stringify(t);
        } else {
            window.location.href = window.location.protocol + "//" + document.querySelector("[data-login_uri]").getAttribute("data-login_uri") + "?token=" + JSON.stringify(t);
        }
    } else {
        throw new IntastellarSolutionsSDKError(e.error);
    }
})

Server-Side verificering

// Check if the token is valid
$issuer = "Intastellar Account";
$issuer_url = "apis.intastellaraccounts.com";

// Check if the origin is a domain & not an IP like 127.0.0.1 and remove the subdomain
// Remove the subdomain if its not a ip address
$access_id = $_SERVER['HTTP_ORIGIN'];

$tokenFromHeader = $_SERVER['HTTP_AUTHORIZATION'];
if (empty($tokenFromHeader)) {
    http_response_code(401);
    echo json_encode([
        "account" => null,
        "error" => "No token provided",
        "statusCode" => 401,
        "status" => "Unauthorized"
    ]);
    exit();
}

// Decode the token & remove the Bearer
$tokenFromHeader = str_replace("Bearer ", "", $tokenFromHeader);
$encodedToken = base64_decode($tokenFromHeader);
$splitToken = explode(".", $encodedToken);

$header = $splitToken[0];
$payload = $splitToken[1];
$signature = $splitToken[2];

// Check if the signature is valid
$hash = hash_hmac("sha256", $header . '.' . $payload, "intastellaraccounts.com", true);
$hash = base64_encode($hash);

if ($hash != $signature) {
    http_response_code(401);
    echo json_encode([
        "error" => "Invalid token",
        "statusCode" => 401,
        "status" => "Unauthorized"
    ]);
    exit();
}

$header = json_decode(base64_decode($splitToken[0]), true);
$payload = json_decode(base64_decode($splitToken[1]), true);

$expires = $header["exp"];
$iss = $header["iss"];
$iss_url = $header["iss_domain"];

if ($expires < time()) {
    http_response_code(401);
    echo json_encode([
        "account" => null,
        "error" => "Token expired",
        "statusCode" => 401,
        "status" => "Unauthorized"
    ]);
    exit();
}

// Remove the subdomain if its not a ip address
if ($access_id != "localhost" && !filter_var($access_id, FILTER_VALIDATE_IP)) {
    $access_id = preg_replace('/^(?:https?://)?(?:www.)?/i', '', $access_id);
}

if ($iss == $issuer && $iss_url == $issuer_url) {
    $salt = $payload["user_id"];
    $email = $payload["email"];
    $scopeFromToken = $payload["allowed_scope"];

    // Getting the user from the database and sending it back to the client
    $publichash = array(
        "account_domain" => "my.intastellaraccounts.com",
        "account_id" => (int)$newsalt["id"],
        "account_url" => "https://my.intastellaraccounts.com",
        "scope" => $scopeFromToken,
        "user" => [
            "user_id" => $newsalt['salt'],
            "name" => ["firstName" => $newsalt["first_name"], "lastName" => $newsalt["last_name"]],
            "username" => $newsalt["username"],
            "email" => $newsalt["email"],
            "lanuage" => $newsalt["language"],
            $scope,
            "avatar" => "https://scontent-uc-d2c-7.intastellaraccounts.com/a/s/ul/p/avtr46-img/" . $image,
            "cover" => "https://scontent-uc-d2c-7.intastellaraccounts.com/a/s/ul/p/avtr46-img/" . $cover
        ]
    );

    http_response_code(200);
    echo json_encode([
        "account" => $publichash,
        "error" => null,
        "statusCode" => 200,
        "status" => "OK"
    ]);
} else {
    http_response_code(401);
    echo json_encode([
        "account" => null,
        "error" => "Invalid token",
        "statusCode" => 401,
        "status" => "Unauthorized"
    ]);
}

Integration

Find dokumentation på Intastellar Developers