Posted by .Ronald on

Een eerste brouwsel

Afgelopen week mijn eerste brouwsel gemaakt. Meteen al veel dingen ondervonden en geleerd voor een volgende keer.

Om te beginnen heb ik een startset voor graanbrouwen aangeschaft, met name de Startset graanbrouwen PRO ELECTRA van Homebrewshop/Braumarkt, samen met een aantal moutpakketten om het brouwen en brouwproces onder de knie te krijgen.

Ik ben begonnen met het Duvels Nat moutpakket van Arsegan. Voor de eerste keren leek een kant-en-klaar pakket mij handig, omdat het dan toch al zeker goed zit wat de ingrediënten betreft.
Dit pakket komt met een gebruiksaanwijzing die je stap per stap kan volgen. Gedurende het brouwen waren niet alle stappen duidelijk voor mij, ik had uit cursus en lectuur anders geleerd. Ondertussen ben ik er ook achter gekomen dat die bijgeleverde gebruiksaanwijzing niet van een hoge kwaliteit is. Wat ik jammer vind voor een pakket dat zich als gebruiksvriendelijk aanprijst, met name naar de beginnende brouwer toe die vaak verwarrende informatie en tegenstrijdige instructies krijgt. Gelukkig zijn er gebruikersfora en lectuur die (achteraf) duidelijkheid verschaffen. Een volgende brouwsel zal ik minder strikt de stappen uit de gebruiksaanwijzing volgen. Het belangrijkste is het brouwschema, en dat is wel duidelijk genoeg.
Mijn eerste ervaring was dat brouwen echt wel wat tijd in beslag neemt. Ik ben er een hele avond en een stukje van de nacht mee bezig geweest. Zo laat zelfs dat ik het schoonmaken van de brouwketel en filterkuip heb laten staan tot de volgende dag. Ik weet het, het is zondigen tegen de reinigingsregels, maar een beetje nachtrust is ook nodig.
Dat brengt mij bij de eerste geleerde les: Begin op tijd!
En met een goede voorbereiding en planning, kunnen de verschillende stappen wat sneller uitgevoerd worden of beter op elkaar worden afgestemd, dat het de volgende keer sneller gedaan zal zijn.

De zaken die ik geleerd heb:

  • Begin op tijd
  • Een giststarter is niet nodig als je met korrelgist werkt, dat scheelt toch ook al een dag
  • Het schroten en het gebruik van de schrootmolen kan beter.
    Ik heb zo’n eenvoudige gietijzeren schrootmolen met schijven. Het afstellen is een beetje zoeken, daar moet ik nog een goede maat en manier van afstellen voor vinden.
    De molen zelf kon ik niet echt goed op de tafel vastzetten. Een extra plankje zou kunnen helpen de volgende keer.
    Het opvangen van de geschrote mout was ook niet vanzelfsprekend. Er past niet meteen een bak onder de molen waardoor ik wat heb moeten improviseren met een bak er schuin onder te zetten en regelmatig in een lege zak over te kiepen. Dit gaf wat extra werk, kostte wat extra tijd en gaf vooral veel stof
  • De thermometer: deze kon ik niet vast maken aan de brouwketel, wat ik onhandig werken vond. Met een lijmklem ofzo denk ik dat de volgende keer op te lossen.
    Een 2e thermometer om bijvoorbeeld het opwarmende spoelwater in de gaten te houden, zou ook handig zijn. Trouwens, een goede thermometer in de keuken is nooit verloren.
  • Ik heb geen maataanduidingen in mijn brouwketel, filterkuip en vergistingsvat. En ik had dus op een gegeven moment totaal geen idee met welke hoeveelheden ik aan het werken was.
    Hiervoor voorzie ik tegen de volgende keer met een meetlat liter per liter de inhoud op te meten en dit in een handige tabel bij de hand te hebben, zodat ik maar de afstand tussen de oppervlakte en de bovenrand dien op te meten en meteen in de tabel de inhoud kan aflezen.
  • Ik had ook (nog) geen mostschep, deze zat niet bij in de startset. Lijkt mij anders vrij essentieel, en is een kleinde tekortkoming van de verder wel vrij complete startset.
    Ik heb mij kunnen behelpen met een maatbeker. Maar dit was niet echt handig werken omwille van de greep die kort tegen de maatbeker zit en de schenktuit die ervoor zorgde dat de bovenste laag van het filterbed werd omgespoeld met mogelijks zuurstof in het wort tot gevolg.
  • Ik heb waarschijnlijk niet genoeg van het eerste gespoelde wort, ongeveer 2 liter, terug in de filterkuip gegoten om echt heldere gefilterde wort te hebben. De volgende keer ga ik daar meer aandacht aan moeten besteden.
  • Doordat ik niet wist hoeveel wort er uiteindelijk na het filteren en spoelen overbleef, was het een beetje gokken. Waarschijnlijk heb ik te weinig spoelwater gebruikt, 4 liter, of had ik verder moeten aanvullen met gewoon water om de gewenste hoeveelheid wort voor het koken te verkrijgen. De tabel met maataanduidingen zal de volgende keer zeker van pas komen.
  • Ik heb een paar keer de zuurtegraad gemeten met pH strips en weet nu ongeveer hoeveel melkzuur ik moet toevoegen. Ik heb er 2 eetlepels aan toegevoegd. Volgende keer nog een keer extra meten om misschien nog een eetlepel melkzuur extra te moeten toevoegen.
  • Het terug aan de kook brengen van het gefilterde wort duurde bijna een uur, wat ik toch wel vrij lang vond. Ik durfde de ketel niet volledig open zetten uit schrik voor aanbranden van het wort.
  • Bij het koken is er volgens mij ook nog veel vocht verloren gegaan. Maar dat weet ik pas zeker als ik de volgende keer de inhoud voor en na het koken kan meten.
  • Voor het afkoelen heb ik het wort even laten rusten om de eiwitten en andere afvalstoffen te laten samenklitten en neerslaan. Bij mij bleven de vlokken ronddwarrelen. Het kwam niet in mij op om te whirlpoolen, hoewel ik er voldoende over had gelezen. De volgende keer zal ik het niet meer vergeten.
  • Bij het koelen met de platenkoeler had ik niet gedacht aan de afvoer van het water naar de spoelbak, en heb ik deze op de rand moeten leggen. Een extra slang wordt voorzien tegen de volgende keer.
  • Bij het toevoegen van de gist in het vergistingsvat heb ik totaal niet verlucht, want ja, bier en lucht zijn de 2 grootste vijanden. Natuurlijk is dit hét moment dat je wel veel zuurstof in je bier moet krijgen voor de gisting. Dit zal ik dan ook geen 2e keer meer vergeten.
  • Ik hoop nu maar dat er voldoende lucht aanwezig was in het vergistingsvat om goed te kunnen gisten. Er zat per slot van rekening véél minder in dan voorzien, ik schat zo’n liter of 15. De vergisting was de volgende ochtend (een paar uurtjes later dus) al wel vlotjes op gang gekomen
  • Het begin SG was 1084, wat een pak hoger licht dan het voorziene 1064. Het feit dat er veel minder vloeistof is overgebleven, zal hiervoor de verklaring zijn. Nu maar hopen dat het bier goed doorgist tot een normaal eind SG. Het zal dan een pak sterker zijn dan voorzien. Anders zal ik op een of andere manier de vergisting een duw in de rug moeten geven, maar dat zie ik dan wel als het zover is.

Voila, al bij al een zeer leerrijke eerste ervaring, die nog lang niet is afgelopen. Het is nu wachten hoe de vergisting verder verloopt voor er kan gelagerd en uiteindelijk gebotteld kan worden. Geduldig wachten nu…

Posted by .Ronald on

Creating an Angular Single Page Application with Azure Active Directory and adal.js that uses an ASP.NET WebAPI

This sample shows how to create a single page application (SPA) that uses Azure Active Directory (AAD) authentication with adal.js and uses an ASP.NET WebAPI with AAD.

The source code for this sample can be found in the angular2-adaljs-webapi GitHub repository.

Set up the applications

  1. Create an Angular application.
    I’ve started from the Angular QuickStart seed to bootstrap an easy to use SPA.
  2. Create a WebAPI application.
    I’ve started in Visual Studio by creating a new ASP.NET Web Application, using the Empty template with Web API folders and core references added to it.

Implementing the WebAPI

I’ll setup the WebAPI first to provide data to the SPA without any authentication

  1. Create a model
    public class Message
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public string Author { get; set; }
        public DateTime PublishedAt { get; set; }
    }
  2. Create a WebAPI 2 controller
    public class MessageController : ApiController
    {
        private IList<Message> _messages = new List<Message>()
        {
            new Message { Id = 1, Title = "Lorem ipsum", Body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", PublishedAt = DateTime.Now},
            new Message { Id = 2, Title = "Pellentesque convallis", Body = "Pellentesque convallis finibus erat, sed lacinia eros mattis quis.", PublishedAt = DateTime.Now},
            new Message { Id = 3, Title = "Maecenas scelerisque", Body = "Maecenas scelerisque pretium risus, eu gravida elit porttitor id.", PublishedAt = DateTime.Now}
        };
    
        public IHttpActionResult Get(int id)
        {
            var message = _messages.FirstOrDefault(m => m.Id == id);
    
            if (message == null)
            {
                return NotFound();
            }
    
            return Ok(message);
        }
    }

This result in a WebAPI that can be consumed like this:

Implementing the Angular single page application

The SPA will consume the WebAPI I’ve created before and show the data on the screen. I will try to follow at least a few of the standards and best practices in Angular development. But bear in mind that this application is not meant to serve as a sample application as such.

  1. Create a model to represent your data
    export class Message {
        Id: number;
        Title: string;
        Body: string;
        Author: string;
        PublishedAt: Date;
    }
  2. Create a service that consumes the WebAPI
    import { Injectable } from '@angular/core';
    import 'rxjs/add/operator/toPromise';
    import { Http } from '@angular/http';
    import { Message } from './message';
    
    @Injectable()
    export class MessageService {
        private messageUrl = 'http://localhost:50071/api/';
    
        constructor(private http: Http) {}
    
        getMessage(id: number): Promise<Message> { 
            return this.http.get(this.messageUrl + 'message/' + id)
                .toPromise()
                .then(response => response.json() as Message)
                .catch(this.handleError);
        }
    
        private handleError(error: any): Promise<any> {
            console.error('An error occurred', error); // for demo purposes only
            return Promise.reject(error.message || error);
        }
    }
  3. Create a component to call the service and display the data
    import { Component } from '@angular/core';
    import { Message } from './message'
    import { MessageService } from './message.service';
    
    @Component({
        selector: 'message',
        template: `<div *ngIf="message; else noMessage">
            <h2>{{message.Title}}</h2>
            <div>{{message.Body}}</div>
            <br />
            <div><label>Author: </label>{{message.Author}}</div>
            <div>{{message.PublishedAt | date:'fullDate'}}</div>
        </div>
        <button (click)="getMessage()">Get message</button>`
    })
    export class MessageComponent {
        messageId: number;
        message: Message;
    
        constructor(private messageService: MessageService) {
            this.messageId = 0;
            this.message = null;
        }
    
        getMessage() {
            this.messageId = Math.floor((Math.random() * 3) + 1);;
            this.messageService.getMessage(this.messageId).then(m => this.message = m);
        }
    }
  4. Add routing and declarations to the Angular app.
    This is what my app.module.ts looks like:

    import { NgModule }      from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { RouterModule } from '@angular/router';
    import { HttpModule } from '@angular/http';
    import { AppComponent }  from './app.component';
    import { MessageComponent } from './message.component';
    import { MessageService } from './message.service';
    
    var routeConfig = [
      {
        path: 'messages',
        component: MessageComponent
      }
    ];
    
    @NgModule({
      imports: [BrowserModule, RouterModule.forRoot(routeConfig), HttpModule ],
      declarations: [ AppComponent, MessageComponent ],
      providers: [ MessageService ],
      bootstrap:    [ AppComponent ]
    })
    export class AppModule { }

    And this is what my app.component.ts looks like:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'my-app',
      template: `<a routerLink="/">HOME</a> <a routerLink="messages">Messages</a>
        <h1>Hello {{name}}</h1>
        <router-outlet></router-outlet>`
    })
    export class AppComponent { 
      name = 'Angular'; 
    }

Enable CORS to allow cross origin web requests

As the Angular application and the WebAPI are served from different hosts, we need to explicitly allow the SPA to consume data from the WebAPI. We do this by enabling CORS in the WebAPI and allow requests originating from the SPA.

  1. Add the CORS package to the WebAPI
    Install-Package Microsoft.AspNet.WebApi.Cors
  2. Enalbe CORS in WebApiConfig.cs
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.EnableCors();
    
            // Web API configuration and services
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
  3. Allow GET request coming from the SPA to the controller
    [EnableCors(origins: "http://localhost:3000", headers: "*", methods: "get")]
    public class MessageController : ApiController
    {

Add Azure Active Directory Authentication to the Angular SPA

I’ve already described how to add AAD authentication to an existing Angular 2 application in a previous blog post Add Azure Active Directory to an existing Angular 2 Single Page Application. I will follow the steps outlined there. This is in short how this is done:

  1. Configure the app to use SSL
  2. Register the application in Azure Active Directory
  3. Configure it to use OAuth2
  4. Implement and configure adal.js
  5. Add login and (optionally) logout functionality to your app that logs in to AAD
  6. Add a route guard to protect routes from unauthorized access and force AAD authentication

I’ve now created an Angular2 SPA that requires Azure Active Directory authentication (in some parts of the application), and that consumes a WebAPI, which not yet requires authentication.

Set up the WebAPI to require authentication

Next step is to set up the ASP.NET WebAPI to require authentication on the service the Angular2 SPA is consuming. This step is also already described in a previous blog post. You can read it here: Add Azure Active Directory to an existing ASP.NET MVC web application.

I am using Visual Studio 2017, so the easiest way for me to add Azure Active Directory authentication is by right-clicking on the Connected Services item in the project

  1. Right-click on the Connected Services item and select Add Connected Service
  2. From the list of connected services select Authentication with Azure Active Directory to configure single sign-on in your application
  3. On the introduction screen of the wizard click Next
  4. On the next screen enter your Domain (tenant) and an App ID URI
    If your WebAPI wasn’t already configured to use SSL, the wizard will do that for you.
  5. Optionally you can click Next to enable Directory access, so the application can read profile information from AAD
  6. Click Finish and the wizard will make the necessary changes to your code like adding Owin middleware, packages the Authorize attribute to the controllers, configure authentication

Tie the ends together

Now with both a WebAPI and a SPA configured to require Azure Active Directory, all I have to do is have them work together. I do this by telling the Angular2 SPA to send a JSON Web Token with every request sent to the WebAPI.

  1. Add the angular2-jwt libraries to the Angular2 SPA
    npm install angular2-jwt --save
  2. Have the route guard acquire the token for the logged in user and store the token in the localStorage. Therefore it needs the App ID URI from the WebAPI service. You can find this in the Azure Portal.
    Add this App ID URI to your AdalJs settings.

    import { Injectable } from '@angular/core';
    @Injectable()
    export class SecretService {
        public get adalConfig(): any {
            return {
                tenant: '[your tenant]',
                clientId: '[a GUID, the application ID]',
                redirectUri: window.location.origin + '/',
                postLogoutRedirectUri: window.location.origin + '/',
                resourceId: "[App ID URI]]"
            };
        }
    }

    Use it to acquire the token in the route guard:

    if (this.adalService.userInfo.isAuthenticated) {
        this.adalService.acquireToken(this.secretService.adalConfig.resourceId)
            .subscribe(tokenOut => localStorage.setItem('id_token', tokenOut));
    
        return true;
    } else {
  3. Change the root URL of the API to its https counterpart and replaced the regular Http provider with the AuthHttp provider in the service
    import { AuthHttp, AuthConfig, AUTH_PROVIDERS, provideAuth } from 'angular2-jwt/angular2-jwt';
    constructor(private http: AuthHttp) {}
  4. Add the provideAuth configuration in app.module.ts to tell it that it needs to add the token from the adal service (from step 2) to each request to the WebAPI that requires authentication
    providers: [MessageService, SecretService, AdalService, RouteGuard, provideAuth({
      tokenGetter: (() => localStorage.getItem('id_token'))
    }) ],
  5. In the Azure Portal, add API access to the WebAPI application to the SPA application
Posted by .Ronald on

Add Azure Active Directory to an existing Angular 2 Single Page Application

This article will guide you through the process of configuring your Single Page Application (SPA) in TypeScript (or JavaScript) to use Azure Active Directory (AAD) authentication.

We will use adal.js Active Directory Authentication Library (ADAL) for JavaScript and ng2-adal (which is built upon adal-angular)

Prepare your application to use Azure Active Directory

  1. If your application doesn’t already use SSL, it is hightly recommended to do it now. AAD without SSL, thus running over an unsecure connection is not advisable and a real hassle to setup.
    Note the SSL url of your application, as you will need it later to register the application in AAD.

Register your application in Azure Active Directory

If you haven’t already registered your application in the Azure Portal, follow the steps below:

  1. Sign in to the Azure portal
  2. Choose Azure Active Directory from your services (search using More Services if it isn’t shown yet)
  3. Choose App registrations and Add
  4. Enter a Name, choose Web app / API for Application Type and enter the URL of your web application under Sign-on URL (without the trailing slash)
    The URL is the SSL url we got earlier when we enable SSL for our web application
  5. Click Create
  6. Still in your application registrations, choose your application, choose All settings and Properties
  7. Copy the Application ID
  8. Enter the Logout URL as the Sign-on URL you entered earlier, followed by /Account/EndSessionThis will link to the single sign out URL of our application
  9. Also from the Settings menu, add a Key with a duration of 1 or 2 years
    Note down the key, as you will not be able to retrieve it afterwards.

Additional steps required for your SPA

Authentication happens using OAuth2 protocol. Applications provisioned in AAD are not by default enabled to use OAuth2, so you need to explicitly opt-in to do so:

  1. Still in the Azure Portal and in the page of the application you created before, click on Manifest to open the manifest editor.
    Alternatively, you can download, edit and upload the manifest afterwards, but the inline manifest editor is much easier to use.
  2. Look for the oauth2AllowImplicitFlow setting, which by default is set to false. Set it to true and save the manifest
    "oauth2AllowImplicitFlow": true,

Implementing and configuring adal.js in your Angular 2 SPA – Overview

In general you will need to follow theses steps, I will explain them in detail further on:

  1. Acquire the adal.js resources
  2. Create a service that provides you with the AAD settings
  3. Create and use a routeguard
  4. Add the Adal services to your application and initialize them
  5. Create a component to login and logout

Acquire the adal.js resources

  1. If you’re using the Node Package Manager (npm) system, it’s as easy as executing 1 single command to pull in the ng2-adal package and all it’s dependencies
    npm install ng2-adal --saveYou can also pull in the ng2-adal package with another package manager or manually. Make sure to also pull in all the required dependencies.
  2. If you’re using a module loader like SystemJS, you will need to add the modules to its configuration file, like it is shown for the systemjs.config.js file for SystemJS:
    (function (global) {
      System.config({
        paths: {
          // paths configuration
        },
        map: {
          // existing map configuration
    
          // adal libraries
          'ng2-adal': 'npm:ng2-adal',
          'adal': 'npm:adal-angular/lib',
          'adal-angular': 'npm:adal-angular/lib',
        },
        packages: {
          // existing package configuration
    
          // adal packages
          'ng2-adal': { main: 'core.js', defaultExtension: 'js' },
          'adal-angular': { main: 'adal-angular', defaultExtension: 'js' },
          'adal': { main: 'adal.js', defaultExtension: 'js' }
        }
      });
    })(this);

Create a service that provides you with the AAD settings

This is a simple angular service that stores the AAD settings, so they are easily manageable and accessible

  1. create a file called secret.service.ts
    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class SecretService {
        public get adalConfig(): any {
            return {
                tenant: '[your tenant]',
                clientId: '[a GUID, the application ID]',
                redirectUri: window.location.origin + '/',
                postLogoutRedirectUri: window.location.origin + '/'
            };
        }
    }

Create and use a routeguard

A route guard is used to control the routers behavior and returns true or false to indicate whether the route can be followed or not

  1. Create an authentication guard (LoggedInGuard.ts) and implement the canActivate() method that checks whether the user is authenticated over Adal, if authenticated returning true, otherwise navigating to a login page
    import { Injectable } from '@angular/core';
    import { Router, CanActivate } from '@angular/router';
    import { AdalService } from 'ng2-adal/core';
    
    @Injectable()
    export class LoggedInGuard implements CanActivate {
        constructor(private adalService: AdalService,
            private router: Router) { }
    
        canActivate() {
            if (this.adalService.userInfo.isAuthenticated) {
                return true;
            } else {
                this.router.navigate(['/login']);
                return false;
            }
        }
    }
  2. Protect the route with the authentication guard in your routing configuration (fragmented code sample shown):
    import { SecretService } from "./secret.service"; 
    import { AdalService } from "ng2-adal/core"; 
    import { LoggedInGuard } from './LoggedInGuard';
    
    // ...
    
    { path: 'protected', component: protectedComponent, canActivate: [LoggedInGuard] },
    
    // ...
    
    providers: [AdalService, SecretService, LoggedInGuard],

Add the Adal services to your application and initialize them

  1. In app.component.ts add following code to import the services
    import { Component, OnInit } from '@angular/core';
    import { SecretService } from './secret.service';
    import { AdalService } from "ng2-adal/core";
  2. Still in app.component.ts initialize the Adal service in the constructor with the settings stored in the Secret service
    export class AppComponent implements OnInit {
        profile: any;
      
        constructor(
            private adalService: AdalService,
            private secretService: SecretService) {
            this.adalService.init(this.secretService.adalConfig);
        }
    }
  3. To prevent the user having to log in every time again, the authentication token is stored in the browser cache. This allows us to try to retrieve this token and continue using the application without being redirected again to the Azure login page.
    Add following code to app.component.ts to get the user object from cache:

        ngOnInit(): void {
            this.adalService.handleWindowCallback();
            this.adalService.getUser();
        }

Create a component to login and logout

This is a very straightforward way to add a login and logout button to your application. The essence is to call the adalService.login() and adalService.logOut() functions. Integrate them in your application to meet your requirements:

  1. Create a login component (login.component.ts)
    import {Component, OnInit} from '@angular/core';
    import {Router} from "@angular/router";
    import {AdalService} from 'ng2-adal/core';
    
    @Component({
        selector: 'welcome',
        template: '<h1>You need to login first</h1><button (click)="logIn()">Login</button>'
    })
    export class LoginComponent {
    
        constructor(
            private router: Router,
            private adalService: AdalService
        ) {
            if (this.adalService.userInfo.isAuthenticated) {
                this.router.navigate(['/home']);
            }
        }
    
        public logIn() {
            this.adalService.login();
        }
    }

    If the user is already logged in with valid Azure Active Directory credentials, he will immediately be redirected to the /home page.
    Otherwise, the user is presented with the Azure login page to login first, and afterwards redirected to the home page URL you provided in the AAD application registration.

  2. Create a logout component (logout.component.ts)
    import {Component} from '@angular/core';
    import {AdalService} from 'ng2-adal/core';
    
    @Component({
        selector: 'logout',
        template: '<div protected><h1>This is the logout page.</h1><button (click)="logOut()">Logout</button></div>'
    })
    export class LogoutComponent {
    
        constructor(
            private adalService: AdalService
        ) {
        }
    
        public logOut() {
            this.adalService.logOut();
        }
    }

    This will sign out the user from Azure Active Directory, invalidate the users authentication token and redirect to the post logout URL.

  3. Add routes to the login and logout components
    import { LoginComponent} from './login.component';
    import { LogoutComponent} from './logout.component';
    { path: 'logout', component: LogoutComponent },
    { path: 'login', component: LoginComponent },
    declarations: [AppComponent, LoginComponent, LogoutComponent, /* ... */ ]

References

Posted by .Ronald on

Add Azure Active Directory to an existing ASP.NET MVC web application

There are 2 options to add Azure Active Directory to your existing ASP.NET MVC application.

The easiest one is in Visual Studio. Right-click on your web project, and you are presented with the possibility to configure Azure AD Authentication. This starts a wizard which will do some checks and configures your application for you. Prerequisites are described on the Diagnosing errors with the Azure Active Directory Connection Wizard page.

The other, and slightly more difficult option, is to configure your application yourself. And that is what’s described below.

Prepare your application to use Azure Active Directory

  1. If your application doesn’t already use SSL, you need to enable it now. AAD without SSL, thus running over an unsecure connection is not advisable and a real hassle to setup.
    Note the SSL url, as you will need it later to register the application in AAD.

Remove existing authentication (if any)

  1. If you have configured your application in web.config to use any form of authentication, remove it
    <system.web>
      <authentication mode="None" />
    </system.web>
  2. If you have any settings in web.config regarding AAD authentication, remove them also
    <add key="ida:ClientId" value="[some GUID]" />
    <add key="ida:AADInstance" value="https://login.microsoftonline.com/" />
    <add key="ida:Domain" value="[your domain]" />
    <add key="ida:TenantId" value="[some guid]" />
    <add key="ida:PostLogoutRedirectUri" value="https://localhost:44364/" />
  3. It might also be interesting to check the .csproj file for any left-over authentication elements. Disable them and only enable anonymous authentication
    <PropertyGroup>
      <IISExpressAnonymousAuthentication>enabled</IISExpressAnonymousAuthentication>
      <IISExpressWindowsAuthentication>disabled</IISExpressWindowsAuthentication>
    </PropertyGroup>
  4. Remove authentication NuGet packages

Register your application in Azure Active Directory

  1. Sign in to the Azure portal
  2. Choose Azure Active Directory from your services (search using More Services if it isn’t shown yet)
  3. Choose App registrations and Add
  4. Enter a Name, choose Web app / API for Application Type and enter the URL of your web application under Sign-on URL (without the trailing slash)
    The URL is the SSL url we got earlier when we enable SSL for our web application
  5. Click Create
  6. Still in your application registrations, choose your application, choose All settings and Properties
  7. Copy the Application ID
  8. Enter the Logout URL as the Sign-on URL you entered earlier, followed by /Account/EndSession
    This will link to the single sign out URL of our application
  9. Also from the Settings menu, add a Key with a duration of 1 or 2 years
    Note down the key, as you will not be able to retrieve it afterwards.

Configure your application to use your Azure AD tenant

  1. Open web.config and add appSettings for:
    <appSettings>
      <add key="ida:ClientId" value="[some GUID]" />
      <add key="ida:AppKey" value="[The key we created earlier]" />
      <add key="ida:Tenant" value="[Tenant name]" />
      <add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
      <add key="ida:RedirectUri" value="[Url of the application]" />
    </appSettings>
    
  2. Replace the AccountController with this code:
    public class AccountController : Controller
    {
        public void SignIn()
        {
            // Send an OpenID Connect sign-in request.
            if (!Request.IsAuthenticated)
            {
                HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
            }
        }
        public void SignOut()
        {
            // Remove all cache entries for this user and send an OpenID Connect sign-out request.
            string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
            AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
            authContext.TokenCache.Clear();
    
            HttpContext.GetOwinContext().Authentication.SignOut(
                OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
        }
    
        public void EndSession()
        {
            if (HttpContext.Request.IsAuthenticated)
            {
                // Remove all cache entries for this user and send an OpenID Connect sign-out request.
                string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
                AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
                authContext.TokenCache.Clear();
            }
    
            // If AAD sends a single sign-out message to the app, end the user's session, but don't redirect to AAD for sign out.
            HttpContext.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
        }
    }

    (Credits for this code go to https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect/)

  3. Add a reference to Microsoft.AspNet.Identity, Microsoft.Owin.Security.OpenIdConnect, Microsoft.Owin.Security.Cookies, Microsoft.IdentityModel.Clients.ActiveDirectory
    PM> Install-Package Microsoft.AspNet.Identity.Owin
    PM> Install-Package Microsoft.Owin.Security.OpenIdConnect
    PM> Install-Package Microsoft.Owin.Security.Cookies
    PM> Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory
  4. Replace the Startup class in Startup.Auth.cs with this code:
    public partial class Startup
    {
        //
        // The Client ID is used by the application to uniquely identify itself to Azure AD.
        // The App Key is a credential used to authenticate the application to Azure AD.  Azure AD supports password and certificate credentials.
        // The Metadata Address is used by the application to retrieve the signing keys used by Azure AD.
        // The AAD Instance is the instance of Azure, for example public Azure or Azure China.
        // The Authority is the sign-in URL of the tenant.
        // The Post Logout Redirect Uri is the URL where the user will be redirected after they sign out.
        //
        private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
        private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
        private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
        private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
        private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
    
        public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
    
        // This is the resource ID of the AAD Graph API.  We'll need this to request a token to call the Graph API.
        string graphResourceId = ConfigurationManager.AppSettings["ida:GraphResourceId"];
    
        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
    
            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions
                {
                    ClientId = clientId,
                    Authority = Authority,
                    PostLogoutRedirectUri = redirectUri,
                    RedirectUri = redirectUri,
    
                    Notifications = new OpenIdConnectAuthenticationNotifications()
                    {
                        //
                        // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
                        //
                        AuthorizationCodeReceived = OnAuthorizationCodeReceived,
                        AuthenticationFailed = OnAuthenticationFailed
                    }
    
                });
        }
    
        private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
        {
            context.HandleResponse();
            context.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
            return Task.FromResult(0);
        }
    
        private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
        {
            var code = context.Code;
    
            ClientCredential credential = new ClientCredential(clientId, appKey);
            string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
            AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
    
            // If you create the redirectUri this way, it will contain a trailing slash.  
            // Make sure you've registered the same exact Uri in the Azure Portal (including the slash).
            Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
    
            AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
        }
    

    (Credits for this code go to https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect/)

  5. Add the NaiveSessionCache utility class from https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect/blob/master/TodoListWebApp/Utils/NaiveSessionCache.cs to your web application
  6. Replace the content of _LoginPartial.cshtml with this code:
    @if (Request.IsAuthenticated)
    {
        <text>
            <ul class="nav navbar-nav navbar-right">
                <li class="navbar-text">
                    Hello, @User.Identity.Name!
                </li>
                <li>
                    @Html.ActionLink("Sign out", "SignOut", "Account")
                </li>
            </ul>
        </text>
    }
    else
    {
        <ul class="nav navbar-nav navbar-right">
            <li>@Html.ActionLink("Sign in", "SignIn", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
        </ul>
    }
  7. Decorate the controllers that require authorization with the [Authorize] attribute
    [Authorize]
    public class HomeController : Controller
    {
        public ActionResult Index()

Now run your application. It should require you to sign in to your Azure AD account and ask your permission to read your user profile, so the application knows who you are.

References

Posted by .Ronald on

How to create a zip archive and download it in ASP.NET

In a previous post How to download multiple files in ASP.NET, I explained how to generate multiple documents and offer them as separate downloads in ASP.NET. One of the options I had when looking for a solution to offer multiple downloads, was adding all the documents to 1 single zip archive container, and offer that as download to the user. This solution didn’t completely satisfy the end-users, but is also offered for those who want to use it.

In this post I will explain, how I take the same list of documents, and offer them as a zip archive to download. Starting from the multiple download solution, this only required 1 extra step in the process, namely, creating a zip archive and adding all the documents to it. The rest of the process is as described in the previous post.

The method takes the same argument as when creating separate download links, namely an a list of byte arrays. Each byte array in its turn contains the binary content of the document. I use the SharpZipLib from ICSharpCode, which can be downloaded here: The Zip, GZip, BZip2 and Tar Implementation For .NET. This is what this method looks like:


Private Function ZipDocuments(ByVal reports As IList(Of byte())) As Boolean

' Add documents to 1 ZIP file, and open in browser
Using zipOutMemoryStream As New MemoryStream()
Using zipOutStream As New ZipOutputStream(zipOutMemoryStream)

'Add documents to Zip File.
Dim cnt As Integer = 1
For Each buffer As byte() In reports
Dim entry As New ZipEntry(String.Format("{0}_{1}.pdf", "GeneratedFile", cnt))

zipOutStream.PutNextEntry(entry)
zipOutStream.Write(buffer, 0, buffer.Length)
zipOutStream.CloseEntry()
cnt += 1
Next

zipOutStream.Finish()
zipOutStream.Close()
zipOutMemoryStream.Close()

Dim responseBytes As Byte() = zipOutMemoryStream.ToArray()

'Return Null on Empty Zip File
Const ZIP_FILE_EMPTY As Integer = 22
If responseBytes.Length <= ZIP_FILE_EMPTY Then
Return Nothing
End If

RegisterDocumentDownload(Guid.NewGuid().ToString(), responseBytes, ContentTypes.ZIP)

End Using
End Using
End Function

I first create a (binary) memorystream (zipOutMemoryStream) to contain the content of the zip file (zipOutStream).
Then I loop over the list of documents (or files), create an entry in the zip file (entry as ZipEntry), and write the binary content to the zip entry.
After adding the files to the zip and cleaning up, I can use the same RegisterDocumentDownload() method from the previous post, and the zip archive will be added to the user and opened in the browser.

And that’s it…