Gå til hovedinnhold

Mobile applikasjoner

Mobile applikasjoner, eller såkalte native apps, er applikasjoner som er programmert for å kjøre lokalt på mobile enheter som smart-telefoner eller nettbrett. Her kan du lese om hvordan du programmerer dine native apps slik at du kan autentisere dine brukere via HelseID.

Forskjellige typer mobile apps

Det vi kaller mobile applikasjoner er en gruppering som omfatter flere forskjellige typer teknologier.

Fellestrekket for disse applikasjonene er at de er utformet og tilpasset mobile enheter som smarttelefoner og nettbrett.

  • Native apps - apps som er kompilert til en spesifikk plattform
    Eksempler: iOS, Android, Xamarin, React Native
  • Hybride apps - er i praksis web apps som er pakket inn i en native app
    Eksempler: Cordova/PhoneGap, Appcelerator Titanium
  • Web apps - web applikasjoner som er spesielt utformet for mobil plattform
    Eksempler: Sencha Touch, Mobile Angular UI 

Autentisering i mobile applikasjoner

Hvordan du programmerer mekanismene for autentisering i mobile klienter avhenger av hvilken type mobil app du lager. For hybride apps og web apps bruker du vanligvis samme framgangsmåte som når man implementerer autentiseringsmekanismer i javascript baserte applikasjoner.

For mobile apps som kompileres til spesifikke plattformer (native apps) er autentiseringsmekanismene lik de vi bruker for tykke klienter.

Beste praksis for mobile applikasjoner

IETF har spesifisert en beste praksis for bruk av OIDC og OAuth 2.0 mekanismene i mobile applikasjoner. Denne anbefalingen finner du beskrevet på IETFs sider: https://tools.ietf.org/html/draft-ietf-oauth-native-apps-12.

Anbefalingene som er gitt i IETFs spesifikasjon er de samme som for tykke klienter som kjører på PC, på sidene våre finner en beskrivelse av hvordan du programmerer autentisering for tykke klienter og hvordan du oppnår SSO mellom tykke klienter og andre applikasjoner.

Klientbibilotek

Med mindre du har mye erfaring å programmere sikkerhetsmekanismer skal du alltid benytte klientbibliotek og mellomvare som profesjonelle sikkerhetsutviklere har programmert. 

HelseID har testet og anbefaler disse klientbibliotekene for mobile applikasjoner:

På OpenId Connects sider over sertifisert programvare finner du forskjellige typer bibliotek og applikasjoner for forskjellige plattformer. Denne oversikten vil endre seg over tid, men vil inneholde de til enhver tid sertifiserte klientbibliotekene som implementerer OpenId Connect protokollen. Oversikten finner du her: http://openid.net/developers/certified/.

Autentisering av brukere med Xamarin.Forms

 Xamarin.Forms er en kryssplattformløsning for programmering av mobile applikasjoner. Koden programmeres i C#, og kan kompileres til forskjellige plattformer som iOS, Android, Windows.

Vårt eksempel er basert på bruk PCL bibliotek i Xamarin.Forms, vi har derfor benyttet IdentityModel.OidcClient klientbibliotek. Dersom du benytter .Net Standard bibliotek kan du bruke klientbiblioteket IdentityModel.OidcClient2.

Fremgangsmåte

På github sidene til IdentityModel.OidcClient finner du instruksjoner for hvordan du setter opp og kommer i gang med klientbiblioteket.

Opprett en Xamarin.Forms page (vi har kalt vår side i vårt eksempel LoginPage.xaml).

 
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HelseID.Demo.Pages.LoginPage">

  <WebView x:Name="LoginWebView"
           HorizontalOptions="Fill"
           VerticalOptions="FillAndExpand">    
  </WebView>

</ContentPage>

Opprett event handlers for Page.Appearing og for WebView.Navigating event som kalles av WebView komponenten.

public LoginPage()
{
   InitializeComponent();
   this.Appearing += LoginPage_Appearing;
   LoginWebView.Navigating += LoginWebViewNavigating;
}

Konfigurasjon av client

I metoden som kalles ved Appearing eventet legger vi inn informasjonen som vil legges ved i autentiseringsforespørselen mot HelseID.

private string _currentCSRFToken;
private void LoginPage_Appearing(object sender, EventArgs e)
{
   //Her starter vi autentiseringsprosessen når login viewet vårt blir synlig
   var authorizeRequest = new AuthorizeRequest("https://helseid-sts.test.nhn.no/");

   var dic = new Dictionary<string, string>(); 
   dic.Add("client_id", Config.ClientId); 
   dic.Add("response_type", "id_token"); 
   dic.Add("scope", "openid profile helseid://scopes/hpr/hpr_number"); 
   dic.Add("redirect_uri", "https://127.0.0.1/min_callback_url"); 
   dic.Add("nonce", Guid.NewGuid().ToString("N"));

   //Vi legger med ett token for å forhindre CSRF 
   _currentCSRFToken = Guid.NewGuid().ToString("N"); 
   dic.Add("state", _currentCSRFToken); 
   
   //Start innloggingsprosess i HelseID 
   var authorizeUri = authorizeRequest.Create(dic); 
   LoginWebView.Source = authorizeUri;
}

Velg grant type (autentiseringsflyt)

Legg merke til hvordan parameteret response_type settes her: 

dic.Add("response_type", "id_token");

Parameteret response_type forteller HelseID hvordan vi ønsker å få utlevert våre tokens. I dette tilfellet sender vi inn verdien "id_token". Dette betyr at vi forteller HelseID at vi forventer å motta tokens via front-kanal (nettleser), og gjør at HelseID følger en såkalt grant type vi kaller implicit flow.

Informasjon om brukeren - Scopes

For autentisering av brukere brukes protokollen OpenId Connect. Du kan lese mer om denne protokollen på siden som omhandler moderne autentiseringsmekanismer og på siden om autentisering.

I OpenId Connect betyr et scope i en autentiseringsforespørsel at du ber om tilgang til en bestemt informasjon om brukeren. I HelseID kaller vi dette en identitetsressurs. Dersom klienten har tilgang til de forespurte scopene blir de returnert i form av claims i identitetstokenet.

I eksempelet over ber vi om flere scopes:

dic.Add("scope", "openid profile helseid://scopes/hpr/hpr_number");

Scopet "openid" må alltid være med i en gyldig OpenId Connect autentiseringsforespørel. De andre forespurte scopene i dette eksempelet betyr at klienten (appen) ber om å få tilgang til annen informasjon om den autentiserte brukeren.


Forespørsel om scopet "profile" returnerer fornavn, mellomnavn, etternavn og fullt navn i tokenet.

Scopet "helseid://scopes/hpr/hpr_number" returnerer brukerens hpr-nummer som et claim i tokenet.

 

Etter vellykket autentisering

Nettleserkomponenten fyrer av et event som heter Navigating før nettleseren navigerer til et nytt dokument. Dette skjer typisk når Url endrer seg, men kan også oppstå av andre årsaker.

Når vi håndterer dette eventet må vi sammenligne verdien på propertien Url i event handlerens EventArgs. Dersom verdien på Url er den samme som verdien vi satte på parameteret "redirect_uri" i autentiseringsforespørselen vår kan vi anta at vi har mottatt en HTTP 302 fra HelseID etter vellykket autentisering.

I eksempelkoden under ser du at vi bruker klientbiblioteket vårt til å håndtere responsen som vi har fått fra HelseID. Klientbiblioteket sørger for å gjøre nødvendig validering av tokens slik at vi kan være sikre på at de vi har mottatt er gyldige og har din applikasjon som mottaker.

Tokens du mottar fra HelseID kommer med base64 encoding, og må omformes til et annet tegnsett før det er lesbart.

private async void LoginWebViewNavigating(object sender, WebNavigatingEventArgs e)
{
   //Hvis URL er lik redirect url som vi definerte i autentiseringsforespørselen
   //antar vi at autentiseringen er "vår"
   if (e.Url.Contains("https://127.0.0.1/min_callback_url"))
   {
      try
      {
         
         await authService.Initialize(e.Url, _currentCSRFToken);
         authorizeResponse = new AuthorizeResponse(e.Url);

         if (authorizeResponse.State != currentCSRFToken)
                  throw new Exception("CSRF token validering feilet");

         //Håndter vellykket autentisering
         var accessToken = authorizeResponse.AccessToken;
         var identityToken = authorizeResponse.IdentityToken;
         LogTokenContent(accessToken, identityToken);
      }
      catch (Exception ex)
      {
         //Håndter feilen som oppstod
      }
   }
}


public void LogTokenContent(string accessToken, string identityToken)
{
	string decodedTokens = "";

	if (!string.IsNullOrWhiteSpace(identityToken))
	{
		decodedTokens += "Identity token: \r\n";
		decodedTokens += DecodeToken(identityToken) + "\r\n";
	}

	if (!string.IsNullOrWhiteSpace(accessToken))
	{
		decodedTokens += "Access token: \r\n";
		decodedTokens += DecodeToken(accessToken);
	}
	Debug.WriteLine(decodedTokens);
}

public static string DecodeToken(string token)
{
	var parts = token.Split('.');

	string partToConvert = parts[1];
	partToConvert = partToConvert.Replace('-', '+');
	partToConvert = partToConvert.Replace('_', '/');
	switch (partToConvert.Length % 4)
	{
		case 0:
			break;
		case 2:
			partToConvert += "==";
			break;
		case 3:
			partToConvert += "=";
			break;
	}

	var partAsBytes = Convert.FromBase64String(partToConvert);
	var partAsUTF8String = Encoding.UTF8.GetString(partAsBytes, 0, partAsBytes.Count());

	return JObject.Parse(partAsUTF8String).ToString();
}