Autentizace aplikací i bez psaní kódu s Azure App Service

Vaše aplikace v Azure platformě můžete zabezpečit s Azure Active Directory včetně vícefaktorového ověřování a synchronizace s on-premise Active Directory bez napsání jediné řádky kódu. Totéž dokážete třeba pro Google, Twitter nebo Microsoft konzumerský účtet. Jak nasadit veřejnou a zabezpečenou část webu? Jak implementovat autorizaci na základě členství uživatele v security group ve vaší aplikaci? A co když chcete využít mocného Microsoft Graph a zjistit třeba jméno nadřízeného přihlášeného uživatele? To všechno si dnes vyzkoušíme.

Jednoduchá autentizace přes Azure Active Directory

Nejjednodušší způsob jak zabezpečit vaši privátní webovou aplikaci v Azure App Service je napojení na Azure Active Directory. Doslova po pár kliknutích bez psaní jakéhokoli kódu získáte robustní ověřování včetně vícefaktorového a díky možnosti synchronizace vašeho Active Directory s Azure Active Directory rovnou s účty a hesly vaší organizace. Pojďme si to vyzkoušet.

Nejprve k aplikaci - použiji primitivní Node.js aplikaci, ale rovnou uděláme jednu důležitou věc. App Service naší serverové aplikaci dává informaci o zalogovaném uživateli do hlavičky requestu, takže můžeme na našem webu rovnou zobrazit přihlášeného uživatele. Mimochodem pokud tuto informaci potřebujete do klientské části aplikace i to pro vás platforma zajistila - ze zalogované klienstké části aplikace se můžete podívat na URI /.auth/me kde najdete totéž (využijeme později). Ale žádný autentizační kód psát nebudeme, to všechno pro nás zajistí platforma.

var express = require('express')
var app = express()

app.get('/', function (req, res) {
    res.send('Prihlasen je ' + req.header('x-ms-client-principal-name'));
})

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function () {
    console.log('Express server posloucha na portu ' + server.address().port);
});

Půjdeme do nastavení aplikace a zapneme autentizaci.

Následuje důležitá volba - co má platforma dělat, pokud se na jakoukoli stránku pokusí přistoupit nepřihlášený uživatel? První volba je nedělat nic (tu si popíšeme až v další kapitolce), tedy platforma to pustí dál. Jinak řečeno vaše aplikace se musí podívat do headeru, kde zjistí jestli je přihlášen nějaký uživatel nebo ne, a rozhodnout se, zda přístup na stránku umožní (veřejná sekce webu) nebo zakáže (privátní sekce webu). Ve druhém případě stačí provést redirect na přihlašovací mechanismus platformy.

My začneme jednodušší situací - jakýkoli přístup bude podléhat autentizaci. Nemusíme tak napsat vůbec žádný kód a přitom vždy máme jistotu, že pouze přihlášený uživatel uvidí jakoukoli z našich stránek. Použít můžeme integraci s Facebook, Google, konzumerské Microsoft účty, Twitter, ale já půjdu do Azure Active Directory (začneme firemní aplikací, řešení pro zákazníky a jejich registraci přidáme později).

Pak klikneme na nastavení AAD autentizační metody.

Nejjednoduší je použít expresní režim. V něm se pro vás automaticky zaregustruje vaše aplikace v AAD a nakonfigurují se potřebné klíče. Velmi jednoduché.

Já ve svém příkladě musím zvolit pokročilý režim, protože moje aplikace poběží v jiném tenantu, než vůči kterému chci ověřovat a to expresní režim neumožňuje (například pokud ve svém vlastním tenantu provozujete jako SaaS dodavatel aplikaci pro zákazníka, která se bude ověřovat vůčit jeho AAD). Postup je následující. V AAD přidám registraci aplikace se Sign-in URL odpovídajícím naší Web App.  Pak se jde do konfigurace této aplikace v AAD a do Reply URL se dá URL naší appky s přidaným /.auth/login/aad/callback. Pak si nakopíruji Appliaction ID (použiji jako Client ID v nastavení ověření) a abychom si mohli vytvořit Issuer URL získáme Directory ID v hlavním nastavení AAD (ta bude https://stm.windows.net/DirectoryID). Pokročilé nastavení App Service autentizace pak vypadá takhle:

Uložme všechna nastavení a v anonymním okně otevřeme naší Web App, tedy mojeauthapp.azurewebsites.net. Místo stránky aplikace budeme přesměrování na přihlašovací stránku.

Zaloguji se.

Povedlo se! A z headeru jsme si v aplikaci načetli kdo je přihlášený.

Žádný kód a při tom velmi bezpečné robustní řešení přihlašování včetně podpory vícefaktorové autentizace a pokročilých řešení zabezpečení jako je Azure Identity Protection. Jednoduché a bez práce. Použití tahle jednoduchá varianta najde i v případě, kdy produkční aplikace používá něco složitějšího a to například v Dev prostředí (vývojář si potřebuje vyzkoušet své změny a tak je pošle do Dev deployment slotu, ale nechce, aby se k aplikaci dostal někdo z veřejnosti) nebo pro beta testery apod.

Jednoduchá autentizace přes Google

Postup nastavení ověřování přes Google je prakticky stejný, jako u Azure Active Directory v pokročilém režimu, kdy jsme registraci prováděli ručně (stejně technologie funguje i s dalšími možnostmi ověřování). Ve vyhládavači najděte stránky Google APIs a vytvořte nový projekt.

Zapneme Google+ API.

Nastavíme jméno aplikace.

Dále musíme vytvořit OAuth ID credentials a zadat callback URL.

Pak si nakopírujeme Client ID.

Tyto údaje zadáme v nastavení autentizace v App Service.

Přepneme přihlašování na Google a pojďme vyzkoušet! Otevřeme nové anonymní okno a připojíme se na stránky. Jsme přesměrování na Google login.

Po přihlášení nám všechno funguje.

Veřejná a privátní část webu

Možná vaše aplikace potřebuje mít veřejně přístupnou část, ale také speciální sekci jen pro přihlášené. To Azure App Service umožňuje, ale musíme zajistit, že se před zobrazením stránky naše aplikace rozhodne co ukázat a co ne. Navíc proces přesměrování na přihlašovací obrazovku je na nás. Například v .NET se to dá řešit poměrně elegantně v rámci web.config.

Nastavíme povolení přístupu i nepřihlášených uživatelů.

Naši aplikaci si teď upravíme. Pokud nebude nikdo přihlášen, zobrazíme stránku umožňující přihlášení pomocí AAD a Google. Půjde o odkazy na .auth/login/provider kde provider v mém případě bude aad nebo google.

var express = require('express')
var app = express()

app.get('/', function (req, res) {
    var aadlogin = 'Prihlaisit se pres <a href="./.auth/login/aad">AAD</a><br>'; 
    var googlelogin = '\nPrihlasit se pres <a href="./.auth/login/google">Google</a><br>'; 
    var user = req.header('x-ms-client-principal-name');
    var stranka = '';
    if (user == undefined) {
        stranka = 'Nikdo neni prihlasen<br>' + aadlogin + googlelogin;
    } else {
        stranka = 'Prihlasen je ' + user;
    }
    res.send(stranka);
})

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function () {
    console.log('Express server posloucha na portu ' + server.address().port);
});

Takhle vypadá stránka u nově otevřeného anonymního okna.

Po přihlášení se obsah naší stránky změní.

Takto primitivně jsme si tedy ukázali, že můžeme v kódu převzít odpovědnost za to, které stránky vyžadují přihlášení a jaké ne a zajistíme odkazy na zalogování případně odhlášení.

Autorizace přes členství ve skupině přečtením claimu

Zůstaneme ve scénáři uživatelů ověřovaných přes Azure Active Directory a ukážeme si jednoduchý způsob, jak zjistit členství zalogovaného uživatele v Security Group. Využijeme toho, že AAD může do claimu poslat členství ve skupinách a tento claim si můžeme bez složitého kódu přečíst na adrese /.auth/me. Nemusíme přistupovat do AAD. Nicméně musím upozornit na omezení počtu vrácených skupin - do claim jich AAD dá maximálně 200 (pokud máte hodně složité prostředí, kdy je uživatel ve stovkách skupin, není to řešení pro vás).

Nejprve zapneme uvádění skupin v claimu. Najděte registrace naší aplikace v AAD (pokud jste použili expresní metodu portál to vytvořil za vás a název bude odpovídat pojmenování aplikace).

Upravíme manifest.

Změňte zasílání skupin z null na SecurityGroup (můžete také použít All, pakliže vás zajímají i distribuční listy).

Já mám dvě skupiny s tím, že můj uživatel je členem jen skupina1.

Pozměníme si teď náš kód. Chceme přistupovat na /.auth/me - to lze udělat jednoduše ze strany klienta, ale já to teď chci ze strany serveru. Zavolám tedy z node.js přes request tuto URL a do požadavku nakopíruji cookie přicházející od klienta. Prozatím jen výstup vezmeme a zparsujeme do JSON a vypíšeme na web.

var express = require('express');
var app = express();
var request = require('request');

app.get('/', function (req, res) {
    var aadlogin = 'Prihlasit se pres <a href="./.auth/login/aad">AAD</a><br>'; 
    var googlelogin = '\nPrihlasit se pres <a href="./.auth/login/google">Google</a><br>'; 
    var user = req.header('x-ms-client-principal-name');
    var stranka = '';
    if (user == undefined) {
        stranka = 'Nikdo neni prihlasen<br>' + aadlogin + googlelogin;
    } else {
        stranka = 'Prihlasen je ' + user + '<br>';
    }
    res.write(stranka);
    
    var options = {
      url: 'https://mojeauthapp.azurewebsites.net/.auth/me',
      headers: {
        "accept-encoding": "identity",
        "cookie": req.header('cookie')
    }
    };
        request(options, function (error, response, body) {
            if (body) {
                json = JSON.parse(body);
                res.write(JSON.stringify(json));
            }
        res.end();
    });
})

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function () {
    console.log('Express server posloucha na portu ' + server.address().port);
});

Otevřeme stránku (nové anononymní okno, ať se znova přihlašujeme) a přihlásíme se přes AAD. Tady bude výstup:

Překopíruji si do Visual Studio Code a použiji Prettify. Kromě jména, příjmení a tak podobně najdeme i sekci groups.

Co je to za ošklivou hodnotu? To je ID skupiny - to dohledáme v GUI.

Jasně, není to nejpohodlnější, ale pro jednodušší scénáře to stačí. Pokročilejší řešení je používat Graph API, ale o tom později.

Pojďme teď náš kód upravit tak, abychom zjistili, zda je uživatel členem skupina1 nebo není.

var express = require('express');
var app = express();
var request = require('request');

app.get('/', function (req, res) {
    var aadlogin = 'Prihlasit se pres <a href="./.auth/login/aad">AAD</a><br>'; 
    var googlelogin = '\nPrihlasit se pres <a href="./.auth/login/google">Google</a><br>'; 
    var user = req.header('x-ms-client-principal-name');
    var stranka = '';
    if (user == undefined) {
        stranka = 'Nikdo neni prihlasen<br>' + aadlogin + googlelogin;
    } else {
        stranka = 'Prihlasen je ' + user + '<br>';
    }
    res.write(stranka);
    
    var options = {
      url: 'https://mojeauthapp.azurewebsites.net/.auth/me',
      headers: {
        "accept-encoding": "identity",
        "cookie": req.header('cookie')
    }
    };
    if (user != undefined) {
        request(options, function (error, response, body) {
            if (body) {
                json = JSON.parse(body);
                var group = 'efe61e7a-59a4-4dd2-9d3d-386077ce22c5';
                var isMember = false;
                for (var i = 0; i < json[0].user_claims.length; i++) {
                    if (json[0].user_claims[i].typ == "groups")
                        if (json[0].user_claims[i].val == group)
                            isMember = true;
                }
            if (isMember) res.write('Uzivatel je clenem skupina1')
                else res.write('Uzivatel neni clenem skupina1');
            res.end();
            }});
        }
        else res.end();
})

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function () {
    console.log('Express server posloucha na portu ' + server.address().port);
});

Nejprve vyzkoušíme uživatele, který je členem skupina1.

A pak jiného, který členem není.

Princip je zřejmý, teď už je na skutečných programátorech začlenit hezky a udržitelně tuto možnost autorizace do své aplikace.

Pokročilá autorizace s Microsoft Graph

Přes /.auth/me dostáváme dost užitečných informací - id, jméno uživatele a dokonce i členství v security group. Nicméně v adresáři je toho daleko víc. Autentizace v App Service vám při správném nastavení umožňí ve jménu přihlášeného uživatele získat token na čtení přes Graph API. Samozřejmě ale toto řešení bych používal skutečně jen u interních aplikací - pro něco spravovaného externím subjektem postačí běžná autentizace/autorizace, která neodhaluje další detaily uživatelů.

Nejprve v Azure Active Directory u registrace naší aplikace klikneme na její práva.

Přidělíme práva pro čtení v adresáři. Jsme upozorněni, že musíme pro  aplikaci zajistit ještě klíč, k tomu se vrátíme.

Teď musíme pro aplikaci vytvořit klíč. Vyplňte název a délku platnosti a při kliknutí na Save se vám ukáže klíč - ten si nakopírujte.

Pro následující krok v nastavení zatím není dostupné GUI v portále. Abychom nemuseli přímo do API, použijeme resource explorer (resources.azure.com). Do additionalLoginParams vložíme

["response_type=code id_token", "resource=https://graph.microsoft.com/v1.0/me/"]

Do clientSecret vložte klíč, který jsme získali dříve.

V tuto chvíli pro nás App Service zajistí oprávnění pro čtení Microsoft Graph. Změníme tedy náš kód tak, aby načetl jméno nadřízeného přihlášeného uživatele.

var express = require('express');
var app = express();
var request = require('request');

app.get('/', function (req, res) {
    var aadlogin = 'Prihlasit se pres <a href="./.auth/login/aad">AAD</a><br>'; 
    var googlelogin = '\nPrihlasit se pres <a href="./.auth/login/google">Google</a><br>'; 
    var user = req.header('x-ms-client-principal-name');
    var stranka = '';
    if (user == undefined) {
        stranka = 'Nikdo neni prihlasen<br>' + aadlogin + googlelogin;
    } else {
        stranka = 'Prihlasen je ' + user + '<br>';
    }
    res.write(stranka);
    
    var options = {
      url: 'https://mojeauthapp.azurewebsites.net/.auth/me',
      headers: {
        "accept-encoding": "identity",
        "cookie": req.header('cookie')
    }
    };
    if (user != undefined) {
        request(options, function (error, response, body) {
            if (body) {
                json = JSON.parse(body);
                var group = 'efe61e7a-59a4-4dd2-9d3d-386077ce22c5';
                var isMember = false;
                for (var i = 0; i < json[0].user_claims.length; i++) {
                    if (json[0].user_claims[i].typ == "groups")
                        if (json[0].user_claims[i].val == group)
                            isMember = true;
                }
            if (isMember) res.write('Uzivatel je clenem skupina1')
                else res.write('Uzivatel neni clenem skupina1');
            graphUrl = 'https://graph.microsoft.com/v1.0/me/manager';
            graphString = {'auth': {'bearer': req.header('x-ms-token-aad-access-token')}};
            request.get(graphUrl, graphString, function (err, response, graphData) {
                managerJson = JSON.parse(graphData);
                res.write('<br>Tvuj nadrizeny je ' + managerJson.displayName)
                res.end();
                });
            }});
        }
        else res.end();
})

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function () {
    console.log('Express server posloucha na portu ' + server.address().port);
});

Celé API je velmi mocné. Co dalšího s ním můžete dělat si prohlédněte zde: https://developer.microsoft.com/en-us/graph/graph-explorer

Otevřeme aplikaci v novém anonymním okně a přihlásíme se. Všimněte si, že naše aplikace teď požaduje nové oprávnění, které musíme přijmout.

Podívejme se na výsledek.

Azure App Service v kombinaci s Azure Active Directory nabízí nesmírně robustní autentizaci a autorizaci a přitom je to daleko jednodušší, než psát si takové funkce samostatně. Pro některé scénáře jsme nemuseli napsat ani jednu řádku kódu. Pro ty složitější nám stačí přidat jen pár drobností a jednoduše se dostaneme k security group uživatele nebo nám platforma zprostředkuje token pro přímý přístup k Microsoft Graph API pro přihlášeného uživatele.

Zbývá nám ještě jedna sada scénářů. Jak řešit registraci a přihlašování zákazníků, tedy osob, které nejsou ve firemní AAD? Jak ověřovat platnost jejich emailu vloženého při registraci? Kolik kódu budu muset napsat? Příště se podíváme na Azure Active Directory B2C v kombinaci s App Service - to přesně tuto problematiku řeší velmi elegantně a joko službu bez nutnosti psát a udržovat složitý kód!

 

 

 



Federace tokenů GitHub Actions s Azure Active Directory pro přístup z vaší CI/CD do Azure bez hesel AAD
Federace vnitřních Kubernetes identit s Azure Active Directory pro přístup k cloudovým službám bez hesel AAD
Privátní leč cenově dostupná WebApp díky Private Link pro App Service v Azure AppService
Postupné nasazování a testování aplikací na lidech - kanárci, A/B, green/blue na příkladu kadeřníka AppService
Moderní autentizace: oprávnění pro procesy běžící v pozadí s AAD AAD