Moodle authentication against ASP.NET identity services database

Picture the scene – you have a custom enrolment application using ASP.NET identity for authentication and from out of nowhere someone decides that the users now need to be able to login to a VLE to complete assignments. Moodle already has a external database plugin so it can’t be too hard, except it doesn’t support the hashing that identity uses.

Given the short timescale to implement and crazy workload I of course went looking to see if anyone else had done this. There are some threads on Stack Exchange where people have tried to do the same thing and lots of info about how the hashing works so I set about porting the code to PHP only to find that someone had already done a much better job than I’d ever do. Thanks MDHearingAid.

So I cloned the repo and set about bodging it into Moodle. My bodge is not pretty but it works. If you want to do the same thing you can download my patch file (apologies for the Zip, WordPress won’t accept plain text files for some reason) and go at it, just don’t judge me too harshly. This is a patch against Moodle 3.8 but will probably/possibly work against other versions.

Obviously you need connectivity to the database that Identity Services is running on. So you’ll probably want to install Microsoft Drivers for PHP for SQL Server if you haven’t already and then set up your connection in Moodle under Site Administration -> Plugins -> Authentication -> External database. The table name will most likely be AspNetUsers. Username = Username , Password = PasswordHash. Under password format you should now see ASP.NET Identity Service or maybe just [[identityservice]] if my patch to the language file didn’t work properly.


Mist/PacketFence Web Auth

Please note this is not supported by PacketFence/Inverse at the time of writing

There aren’t really any guides out there for Mist and Packetfence integration. During our time working with the Mist engineers we were able to get the authentication services working between Mist and PacketFence. We’ll submit this as a PR to Packetfence in the hope that it’s included in the main release.

Mist Configuration

To make this work you need to configure the Mist system with an SSID with the following security parameters:

Security Settings
Radius Settings
CoA Settings

From here the Mist system attempts to authenticate the device using RADIUS-MAB against the PacketFence system. Should PacketFence not authenticate the device, it returns a redirect request for the device which the Mist AP picks up and forwards to the client so that they can register.

The CoA settings are required here as, should the user no longer become registered e.g. PacketFence times out the device, then a CoA is sent to the Mist system for the device to be disassociated from the wireless environment.

PacketFence Configuration

Firstly, the perl script will need to be installed into the PacketFence environment so PacketFence understand hows to communicate with Mist and to perform the CoA requests as per the configuration. To access this please visit here:

Mist uses the APs as a RADIUS Authenticator so each AP will need to be installed as a “Switch” in the PacketFence configuration. We have created a script to do this as PacketFence do not provide a POST in their API Documentation for config/switches as well as deployment of RADIUS to NPS which is available here:

However, should you only need to deploy one or two APs as part of a PoC then the switch settings required are as follows:

IP Address: Ip Address of the AP
Type: Cisco::Mist
Mode: Production
Deauthentication Method: RADIUS
Use CoA: Yes

Role by VLAN ID: Off
Role by Switch Role: Off
Role by Access List: Off
Role by Web Auth URL: On

Secret Passphrase: RadiusSecretKey


Mist – NPS and PacketFence Radius Scripts

Here is a generic script for importing Mist APs into Microsoft’s NPS RADIUS Server and Packetfence’s Switching configurations:

Mist Setup

$Headers = @{
Authorization = "Token xWH84fgSnZTBMfA2eC9azGqNR2RFfgpmRGo9FGbaw0DlTm6enmfrK0cxkIYtEhdEvvRZesWddU222vHT82hnb0eSZecswe1iWl9h7C"
$Sites = @("771cb8f4-83ac-4385-bf4b-a68a61a8c853", "32f7bd38-9f01-4a3e-81b0-d4afbbc10f12", "e6f6d5c8-4dc9-4a55-ba76-1a903ec5d3f4", "d1320dcc-1e38-4d10-8518-19d844c119f4", "c8d2c1ea-76dd-4183-8cd4-5efcf3de6c4a", "b67b5694-03c2-4155-9b3d-751484b58c65", "dfcd013d-17b3-4805-9b91-2c86f70f3936" )
$SiteNames = @("Site 1", "Site 2", "Site 3", "Site 4", "Site 5", "Site 6", "Site 7")

packetfence setup

$LoginParams = @{"username"="admin";"password"="supersecretpassword"}
$PFLogin = Invoke-WebRequest -Uri "" -Method POST -Body ($LoginParams|ConvertTo-JSON)
$PFToken = ConvertFrom-Json $PFLogin.Content
$PFToken = $PFToken.token
$PFHeaders = @{
Authorization = "Bearer $PFToken"

System Loop

for($i=0; $i -lt $Sites.length; $i++)
	Write-Host "Performing checks on site:" $SiteNames[$i]
	$Uri = "" + $Sites[$i] + "/stats/devices"
	$APStats = Invoke-WebRequest -Uri $Uri -Headers $Headers -ContentType "application/json"
	$Converted = ConvertFrom-Json $APStats
	$CurrentNPSClients = Get-NpsRadiusClient
	Foreach($AP in $Converted) {
	#check NPS and if the RADIUS Client doesn't exist create a new entry
	$ClientCheck = $false
	$NewNameObject = $
	$CurrentNPSClients | ForEach-Object {
		If($_.Name -eq $NewNameObject)
			$ClientCheck = $true
	Write-Host "Is the AP already configured in NPS:" $ClientCheck
	if($ClientCheck -eq $false)
		New-NpsRadiusClient -Name $ -Address $AP.ip -SharedSecret "RadiusSharedSecret"

		#check PacketFence to see whether there is a RADIUS Client if not create one
			$URIPF = "" + $AP.ip
			$PFSwitch = Invoke-WebRequest -Uri $URIPF  -Headers $PFHeaders -ContentType "application/json"
			Write-Host "The AP already configured in PacketFence:" $AP.ip
			Write-Host "Adding the following AP to PacketFence:" $AP.ip
			$IP = $AP.ip
			$Desc = $
			$PostValues = @{"AccessListMap"=$null;"ExternalPortalEnforcement"="Y";"REJECTAccessList"=$null;"REJECTRole"=$null;"REJECTUrl"=$null;"REJECTVlan"=$null;"RoleMap"=$null;"SNMPAuthPasswordRead"=$null;"SNMPAuthPasswordTrap"=$null;"SNMPAuthPasswordWrite"=$null;"SNMPAuthProtocolRead"=$null;"SNMPAuthProtocolTrap"=$null;"SNMPAuthProtocolWrite"=$null;"SNMPCommunityRead"=$null;"SNMPCommunityTrap"=$null;"SNMPCommunityWrite"=$null;"SNMPEngineID"=$null;"SNMPPrivPasswordRead"=$null;"SNMPPrivPasswordTrap"=$null;"SNMPPrivPasswordWrite"=$null;"SNMPPrivProtocolRead"=$null;"SNMPPrivProtocolTrap"=$null;"SNMPPrivProtocolWrite"=$null;"SNMPUserNameRead"=$null;"SNMPUserNameTrap"=$null;"SNMPUserNameWrite"=$null;"SNMPVersion"=$null;"SNMPVersionTrap"=$null;"UrlMap"="Y";"VlanMap"="N";"VoIPCDPDetect"=$null;"VoIPDHCPDetect"=$null;"VoIPEnabled"=$null;"VoIPLLDPDetect"=$null;"cliAccess"=$null;"cliEnablePwd"=$null;"cliPwd"=$null;"cliTransport"=$null;"cliUser"=$null;"coaPort"=$null;"controllerIp"=$null;"deauthMethod"="RADIUS";"defaultAccessList"=$null;"defaultRole"=$null;"defaultUrl"=$null;"defaultVlan"=$null;"description"="$Desc";"disconnectPort"=$null;"gamingAccessList"=$null;"gamingRole"=$null;"gamingUrl"=$null;"gamingVlan"=$null;"group"="default";"guestAccessList"=$null;"guestRole"=$null;"guestUrl"=$null;"guestVlan"=$null;"id"="$IP";"inlineAccessList"=$null;"inlineRole"=$null;"inlineTrigger"=$null;"inlineUrl"=$null;"inlineVlan"=$null;"isolationAccessList"=$null;"isolationRole"=$null;"isolationUrl"=$null;"isolationVlan"=$null;"macSearchesMaxNb"=$null;"macSearchesSleepInterval"=$null;"mac_trigger"=$null;"mode"=$null;"port_trigger"=$null;"radiusSecret"="RadiusSharedSecret";"registrationAccessList"=$null;"registrationRole"=$null;"registrationUrl"="";"registrationVlan"=$null;"ssid_trigger"=$null;"type"="Cisco::Mist";"uplink"=$null;"uplink_dynamic"="dynamic";"useCoA"="Y";"voiceAccessList"=$null;"voiceRole"=$null;"voiceUrl"=$null;"voiceVlan"=$null;"wsPwd"=$null;"wsTransport"=$null;"wsUser"=$null}
			$PFAddSwitch = Invoke-WebRequest -Uri "" -Method POST -Headers $PFHeaders -Body ($PostValues|ConvertTo-JSON)

Office 365

Restricting presenters in MS Teams meetings

You might want to prevent everyone in a meeting from being able to present, perhaps you work in Education or something. You can only do that via Powershell at the moment, because Microsoft.

Install SfB Online Powershell Module

Open Powershell

Import-Module SkypeOnlineConnector
$sfbSession = New-CsOnlineSession
Import-PSSession $sfbSession

or if, like me, you’re in a hybrid set up and your account is homed On-Premise you’ll need to do

Import-Module SkypeOnlineConnector
$sfbSession = New-CsOnlineSession -OverrideAdminDomain ""
Import-PSSession $sfbSession

Once connected you can see your meeting policies and the current DesignatedPresenterRoleMode with

Get-CsTeamsMeetingPolicy | ft Identity, DesignatedPresenterRoleMode

Set your policy to allow only the organiser to present by default and allow the presenter to override this setting. I’m going to do this globally but obviously replace the identifier with whatever policy name you want to set.

Set-CsTeamsMeetingPolicy -Identity Global -DesignatedPresenterRoleMode OrganizerOnlyUserOverride

You can set four options here, which are self explanatory



Full details at


Mist AzureAD SAML configuration

Login to your Azure portal and go to Azure Active Directory -> Enterprise Applications -> New Application. Click “Non-Gallery Application”, enter whatever name you wish to use for the Application, e.g. “Mist” then Add. Go to the new application, click “Single Sign-On” and select “SAML”. Copy the “AzureAD Identifier” from section 4.

Click on the pencil icon next to SAML Signing Certificate and set “Signing option” to “Sign SAML response and assertion”

Download “Certificate (Base64)”

Login to and go to Organisation -> Settings. Under Single Sign-On click “Add IDP”. Enter a name such as “AzureAD” and complete the fields as per the below table. For the certificate, open the “Certificate (Base64)” file that you downloaded from your Azure app in a text editor, copy the entire content of the file and paste into the “Certificate” box.

The rest of the fields can be copied and pasted from your Azure app as follows

Custom Logout URLLogout URL
IssuerAzureAD Identifier
CertificateContent of downloaded “Certificate (Base64)”

Next, we need to complete the configuration of Azure. You can do this manually, but it is easier to download your metadata file from Mist and upload it to Azure.

The URL for contains your organisation ID, i.e.{orgid} copy your orgid from this link and go to{orgid}/ssos you’ll see a JSON object returned, one of the elements will be “id”: “{ssoid} copy your ssoid from this link and go to{orgid}/ssos/{ssoid}/metadata.xml which will give you your metadata.xml file. Save this somewhere.

Go to your AzureAD application -> Single Sign On – > SAML -> Upload metadata file and select the metadata.xml you downloaded.

You now have SAML set up between AzureAD and Mist, but you can’t login as you’re not currently passing a “Role” attribute.

Firstly, log into Mist -> Organization -> Settings. Under “Single Sign-on” click Create Role. For “Name” enter a sensible value for each role that you wish to set up.

It probably makes most sense to use AzureAD group memberships for this. There are several ways to do this, but we do it like this.

Go back to your Azure Mist app -> Single Sign-on. Click the pencil button next to “User Attributes & Claims” then “Add a new claim”. Enter the name as “Role”, source as Attribute and then enter “None” in the “Source attribute”, this ensures people that aren’t in the correct groups get a role of “None” which prevents them from logging in.

Expand “Claim conditions” then in the “User type” dropdown select “Members”. Click “Select groups” and select the relevant group. For “Source” select Attribute and for “Value” type in the Mist role name you wish to assign to users that are members of this group. Continue to add groups for your desired roles.

In your Azure Mist app you’ll need to grant access to users at “Users and Groups”. Add the relevant groups to allow access. Users will see Mist in the Office 365 launcher under


Workrite quick and dirty user creation from AD when you already have SSO

This is ugly but it’ll get you started, there are definitely better ways. User creation script for

[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

$Workriteuser="{workrite username}"
$WorkritePW="{workrite password}"
$WorkriteID="{workrite ID}"


$BaseOUS| ForEach-Object{

	get-aduser -properties mail,employeenumber -searchbase $_ -filter {enabled -eq "True" -and emailaddress -like "*"} -resultsetsize 10000 | foreach-object {

	$mailaddress = $_.mail
	$firstname = $_.givenname
	$surname = $_.surname
	$employeeid = $_.employeenumber


	$url = $url -replace '\s',''

	[xml]$output = Invoke-WebRequest "$url"
		#API response 13 duplicate username
		if ($"#text" -eq "13")
			Write-Host "User $mailaddress already exists"
		#API Response 0 success
		elseif ($"#text" -eq "0")
			Write-Host "User $mailaddress successfully created"