Skip to content

Commit 19e2cf5

Browse files
authored
Update Build a SCIM endpoint with Azure AD
Added more information for Step 3: Build a SCIM endpoint
1 parent 6c9ddd4 commit 19e2cf5

File tree

1 file changed

+359
-3
lines changed

1 file changed

+359
-3
lines changed

articles/active-directory/app-provisioning/use-scim-to-provision-users-and-groups.md

Lines changed: 359 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -754,10 +754,366 @@ TLS 1.2 Cipher Suites minimum bar:
754754

755755
## Step 3: Build a SCIM endpoint
756756

757-
Now that you have designed your schema and understood the Azure AD SCIM implementation, you can get started developing your SCIM endpoint. Rather than starting from scratch and building the implementation completely on your own, you can rely on a number of open source SCIM libraries published by the SCIM commuinty.
758-
The open source .NET Core [reference code](https://aka.ms/SCIMReferenceCode) published by the Azure AD provisioning team is one such resource that can jump start your development. Once you've built your SCIM endpoint, you'll want to test it out. You can use the collection of [postman tests](https://github.com/AzureAD/SCIMReferenceCode/wiki/Test-Your-SCIM-Endpoint) provided as part of the reference code or run through the sample requests / responses provided [above](https://docs.microsoft.com/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#user-operations).
757+
Now that you have designed your schema and understood the Azure AD SCIM implementation, you can get started developing your SCIM endpoint. Rather than starting from scratch and building the implementation completely on your own, you can rely on a number of open source SCIM libraries published by the SCIM community.
759758

760-
Note: The reference code is intended to help you get started building your SCIM endpoint and is provided "AS IS." Contributions from the community are welcome to help build and maintain the code.
759+
The open source .NET Core [reference code](https://aka.ms/SCIMReferenceCode) published by the Azure AD provisioning team is one such resource that can jump start your development. Once you have built your SCIM endpoint, you will want to test it out. You can use the collection of [postman tests](https://github.com/AzureAD/SCIMReferenceCode/wiki/Test-Your-SCIM-Endpoint) provided as part of the reference code or run through the sample requests / responses provided [above](https://docs.microsoft.com/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#user-operations).
760+
761+
> [!Note]
762+
> The reference code is intended to help you get started building your SCIM endpoint and is provided "AS IS." Contributions from the community are welcome to help build and maintain the code.
763+
764+
The solution is composed of two projects, Microsoft.SCIM and Microsoft.SCIM.WebHostSample.
765+
766+
The Microsoft.SCIM project is the library that defines the components of the web service that conforms to the SCIM specification. It declares the interface Microsoft.SCIM.IProvider, requests are translated into calls to the provider’s methods, which would be programmed to operate on an identity store.
767+
768+
![Breakdown: A request translated into calls to the provider's methods](media/use-scim-to-provision-users-and-groups/scim-figure-3.png)
769+
770+
The Microsoft.SCIM.WebHostSample project is a Visual Studio ASP.NET Core Web Application, based on the ***Empty*** template. This allows the sample code to be deployed as standalone, hosted in containers or within Internet Information Services. It also implements the Microsoft.SCIM.IProvider interface using in memory classes as the sample identity store.
771+
772+
```csharp
773+
public class Startup
774+
{
775+
...
776+
public IMonitor MonitoringBehavior { get; set; }
777+
public IProvider ProviderBehavior { get; set; }
778+
779+
public Startup(IWebHostEnvironment env, IConfiguration configuration)
780+
{
781+
...
782+
this.MonitoringBehavior = new ConsoleMonitor();
783+
this.ProviderBehavior = new InMemoryProvider();
784+
}
785+
...
786+
```
787+
788+
### Building a custom SCIM endpoint
789+
790+
The service must have an HTTP address and server authentication certificate of which the root certification authority is one of the following names:
791+
* CNNIC
792+
* Comodo
793+
* CyberTrust
794+
* DigiCert
795+
* GeoTrust
796+
* GlobalSign
797+
* Go Daddy
798+
* VeriSign
799+
* WoSign
800+
801+
The .NET Core SDK includes an HTTPS development certificate that can be used during development, the certificate is installed as part of the first-run experience. Depending on how you run the ASP.NET Core Web Application it will listen to a different port:
802+
803+
* Microsoft.SCIM.WebHostSample: https://localhost:5001
804+
* IIS Express: https://localhost:44359/
805+
806+
For more information on HTTPS in ASP.NET Core use the following link: [Enforce HTTPS in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-3.1&tabs=visual-studio)
807+
808+
### Handling endpoint authentication
809+
810+
Requests from Azure Active Directory include an OAuth 2.0 bearer token. Any service receiving the request should authenticate the issuer as being Azure Active Directory for the expected Azure Active Directory tenant.
811+
In the token, the issuer is identified by an iss claim, like "iss":"https://sts.windows.net/cbb1a5ac-f33b-45fa-9bf5-f37db0fed422/". In this example, the base address of the claim value, https://sts.windows.net, identifies Azure Active Directory as the issuer, while the relative address segment, ***cbb1a5ac-f33b-45fa-9bf5-f37db0fed422***, is a unique identifier of the Azure Active Directory tenant for which the token was issued.
812+
813+
The audience for the token will be the application template ID for the application in the gallery, each of the applications registered in a single tenant may receive the same `iss` claim with SCIM requests. The application template ID for each application in the gallery varies, please contact [email protected] for questions around the application template ID for a gallery application. The application template ID for all custom apps is ***8adf8e6e-67b2-4cf2-a259-e3dc5476c621***.
814+
815+
In the sample code, requests are authenticated using the Microsoft.AspNetCore.Authentication.JwtBearer package. The following code enforces that requests to any of the service’s endpoints are authenticated using the bearer token issued by Azure Active Directory for a specified tenant:
816+
817+
```csharp
818+
public void ConfigureServices(IServiceCollection services)
819+
{
820+
if (_env.IsDevelopment())
821+
{
822+
...
823+
}
824+
else
825+
{
826+
services.AddAuthentication(options =>
827+
{
828+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
829+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
830+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
831+
})
832+
.AddJwtBearer(options =>
833+
{
834+
options.Authority = " https://sts.windows.net/cbb1a5ac-f33b-45fa-9bf5-f37db0fed422/";
835+
options.Audience = "8adf8e6e-67b2-4cf2-a259-e3dc5476c621";
836+
...
837+
});
838+
}
839+
...
840+
}
841+
842+
public void Configure(IApplicationBuilder app)
843+
{
844+
...
845+
app.UseAuthentication();
846+
app.UseAuthorization();
847+
...
848+
}
849+
```
850+
851+
A bearer token is also required to use of the provided [postman tests](https://github.com/AzureAD/SCIMReferenceCode/wiki/Test-Your-SCIM-Endpoint) and perform local debugging using localhost. The sample code uses ASP.NET Core environments to change the authentication options during development stage and enable the use a self-signed token.
852+
853+
For more information on multiple environments in ASP.NET Core use the following link: [Use multiple environments in ASP.NET Core](
854+
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-3.1)
855+
856+
The following code enforces that requests to any of the service’s endpoints are authenticated using a bearer token signed with a custom key:
857+
858+
```csharp
859+
public void ConfigureServices(IServiceCollection services)
860+
{
861+
if (_env.IsDevelopment())
862+
{
863+
services.AddAuthentication(options =>
864+
{
865+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
866+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
867+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
868+
})
869+
.AddJwtBearer(options =>
870+
{
871+
options.TokenValidationParameters =
872+
new TokenValidationParameters
873+
{
874+
ValidateIssuer = false,
875+
ValidateAudience = false,
876+
ValidateLifetime = false,
877+
ValidateIssuerSigningKey = false,
878+
ValidIssuer = "Microsoft.Security.Bearer",
879+
ValidAudience = "Microsoft.Security.Bearer",
880+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A1B2C3D4E5F6A1B2C3D4E5F6"))
881+
};
882+
});
883+
}
884+
```
885+
886+
Send a GET request to the Token controller to get a valid bearer token, the method GenerateJSONWebToken is responsible to create a token matching the parameters configured for development:
887+
888+
```csharp
889+
private string GenerateJSONWebToken()
890+
{
891+
// Create token key
892+
SymmetricSecurityKey securityKey =
893+
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A1B2C3D4E5F6A1B2C3D4E5F6"));
894+
SigningCredentials credentials =
895+
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
896+
897+
// Set token expiration
898+
DateTime startTime = DateTime.UtcNow;
899+
DateTime expiryTime = startTime.AddMinutes(120);
900+
901+
// Generate the token
902+
JwtSecurityToken token =
903+
new JwtSecurityToken(
904+
"Microsoft.Security.Bearer",
905+
"Microsoft.Security.Bearer",
906+
null,
907+
notBefore: startTime,
908+
expires: expiryTime,
909+
signingCredentials: credentials);
910+
911+
string result = new JwtSecurityTokenHandler().WriteToken(token);
912+
return result;
913+
}
914+
```
915+
916+
### Handling provisioning and deprovisioning of users
917+
918+
***Example 1. Query the service for a matching user***
919+
920+
Azure Active Directory queries the service for a user with an externalId attribute value matching the mailNickname attribute value of a user in Azure AD. The query is expressed as a Hypertext Transfer Protocol (HTTP) request such as this example, wherein jyoung is a sample of a mailNickname of a user in Azure Active Directory.
921+
922+
>[!NOTE]
923+
> This is an example only. Not all users will have a mailNickname attribute, and the value a user has may not be unique in the directory. Also, the attribute used for matching (which in this case is externalId) is configurable in the [Azure AD attribute mappings](customize-application-attributes.md).
924+
925+
```
926+
GET https://.../scim/Users?filter=externalId eq jyoung HTTP/1.1
927+
Authorization: Bearer ...
928+
```
929+
930+
In the sample code the request is translated into a call to the QueryAsync method of the services provider. Here is the signature of that method:
931+
932+
```csharp
933+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
934+
// Microsoft.SCIM.IRequest is defined in
935+
// Microsoft.SCIM.Service.
936+
// Microsoft.SCIM.Resource is defined in
937+
// Microsoft.SCIM.Schemas.
938+
// Microsoft.SCIM.IQueryParameters is defined in
939+
// Microsoft.SCIM.Protocol.
940+
941+
Task<Resource[]> QueryAsync(IRequest<IQueryParameters> request);
942+
```
943+
944+
In the sample query, for a user with a given value for the externalId attribute, values of the arguments passed to the QueryAsync method are:
945+
946+
* parameters.AlternateFilters.Count: 1
947+
* parameters.AlternateFilters.ElementAt(0).AttributePath: "externalId"
948+
* parameters.AlternateFilters.ElementAt(0).ComparisonOperator: ComparisonOperator.Equals
949+
* parameters.AlternateFilter.ElementAt(0).ComparisonValue: "jyoung"
950+
951+
***Example 2. Provision a user***
952+
953+
If the response to a query to the web service for a user with an externalId attribute value that matches the mailNickname attribute value of a user doesn't return any users, then Azure Active Directory requests that the service provision a user corresponding to the one in Azure Active Directory. Here is an example of such a request:
954+
955+
```
956+
POST https://.../scim/Users HTTP/1.1
957+
Authorization: Bearer ...
958+
Content-type: application/scim+json
959+
{
960+
"schemas":
961+
[
962+
"urn:ietf:params:scim:schemas:core:2.0:User",
963+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0User"],
964+
"externalId":"jyoung",
965+
"userName":"jyoung",
966+
"active":true,
967+
"addresses":null,
968+
"displayName":"Joy Young",
969+
"emails": [
970+
{
971+
"type":"work",
972+
"value":"[email protected]",
973+
"primary":true}],
974+
"meta": {
975+
"resourceType":"User"},
976+
"name":{
977+
"familyName":"Young",
978+
"givenName":"Joy"},
979+
"phoneNumbers":null,
980+
"preferredLanguage":null,
981+
"title":null,
982+
"department":null,
983+
"manager":null}
984+
```
985+
986+
In the sample code the request is translated into a call to the CreateAsync method of the services provider. Here is the signature of that method:
987+
988+
```csharp
989+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
990+
// Microsoft.SCIM.IRequest is defined in
991+
// Microsoft.SCIM.Service.
992+
// Microsoft.SCIM.Resource is defined in
993+
// Microsoft.SCIM.Schemas.
994+
995+
Task<Resource> CreateAsync(IRequest<Resource> request);
996+
```
997+
998+
In a request to provision a user, the value of the resource argument is an instance of the Microsoft.SCIM.Core2EnterpriseUser class, defined in the Microsoft.SCIM.Schemas library. If the request to provision the user succeeds, then the implementation of the method is expected to return an instance of the Microsoft.SCIM.Core2EnterpriseUser class, with the value of the Identifier property set to the unique identifier of the newly provisioned user.
999+
1000+
***Example 3. Query the current state of a user***
1001+
1002+
To update a user known to exist in an identity store fronted by an SCIM, Azure Active Directory proceeds by requesting the current state of that user from the service with a request such as:
1003+
1004+
```
1005+
GET ~/scim/Users/54D382A4-2050-4C03-94D1-E769F1D15682 HTTP/1.1
1006+
Authorization: Bearer ...
1007+
```
1008+
1009+
In the sample code the request is translated into a call to the RetrieveAsync method of the services provider. Here is the signature of that method:
1010+
1011+
```csharp
1012+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
1013+
// Microsoft.SCIM.IRequest is defined in
1014+
// Microsoft.SCIM.Service.
1015+
// Microsoft.SCIM.Resource and
1016+
// Microsoft.SCIM.IResourceRetrievalParameters
1017+
// are defined in Microsoft.SCIM.Schemas
1018+
1019+
Task<Resource> RetrieveAsync(IRequest<IResourceRetrievalParameters> request);
1020+
```
1021+
1022+
In the example of a request to retrieve the current state of a user, the values of the properties of the object provided as the value of the parameters argument are as follows:
1023+
1024+
* Identifier: "54D382A4-2050-4C03-94D1-E769F1D15682"
1025+
* SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
1026+
1027+
***Example 4. Query the value of a reference attribute to be updated***
1028+
1029+
If a reference attribute is to be updated, then Azure Active Directory queries the service to determine whether the current value of the reference attribute in the identity store fronted by the service already matches the value of that attribute in Azure Active Directory. For users, the only attribute of which the current value is queried in this way is the manager attribute. Here is an example of a request to determine whether the manager attribute of a user object currently has a certain value:
1030+
In the sample code the request is translated into a call to the QueryAsync method of the services provider. The value of the properties of the object provided as the value of the parameters argument are as follows:
1031+
1032+
* parameters.AlternateFilters.Count: 2
1033+
* parameters.AlternateFilters.ElementAt(x).AttributePath: "ID"
1034+
* parameters.AlternateFilters.ElementAt(x).ComparisonOperator: ComparisonOperator.Equals
1035+
* parameters.AlternateFilter.ElementAt(x).ComparisonValue: "54D382A4-2050-4C03-94D1-E769F1D15682"
1036+
* parameters.AlternateFilters.ElementAt(y).AttributePath: "manager"
1037+
* parameters.AlternateFilters.ElementAt(y).ComparisonOperator: ComparisonOperator.Equals
1038+
* parameters.AlternateFilter.ElementAt(y).ComparisonValue: "2819c223-7f76-453a-919d-413861904646"
1039+
* parameters.RequestedAttributePaths.ElementAt(0): "ID"
1040+
* parameters.SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
1041+
1042+
Here, the value of the index x can be 0 and the value of the index y can be 1, or the value of x can be 1 and the value of y can be 0, depending on the order of the expressions of the filter query parameter.
1043+
1044+
***Example 5. Request from Azure AD to an SCIM service to update a user***
1045+
1046+
Here is an example of a request from Azure Active Directory to an SCIM service to update a user:
1047+
1048+
```
1049+
PATCH ~/scim/Users/54D382A4-2050-4C03-94D1-E769F1D15682 HTTP/1.1
1050+
Authorization: Bearer ...
1051+
Content-type: application/scim+json
1052+
{
1053+
"schemas":
1054+
[
1055+
"urn:ietf:params:scim:api:messages:2.0:PatchOp"],
1056+
"Operations":
1057+
[
1058+
{
1059+
"op":"Add",
1060+
"path":"manager",
1061+
"value":
1062+
[
1063+
{
1064+
"$ref":"http://.../scim/Users/2819c223-7f76-453a-919d-413861904646",
1065+
"value":"2819c223-7f76-453a-919d-413861904646"}]}]}
1066+
```
1067+
1068+
In the sample code the request is translated into a call to the UpdateAsync method of the services provider. Here is the signature of that method:
1069+
1070+
```csharp
1071+
// System.Threading.Tasks.Tasks and
1072+
// System.Collections.Generic.IReadOnlyCollection<T> // are defined in mscorlib.dll.
1073+
// Microsoft.SCIM.IRequest is defined in
1074+
// Microsoft.SCIM.Service.
1075+
// Microsoft.SCIM.IPatch,
1076+
// is defined in Microsoft.SCIM.Protocol.
1077+
1078+
Task UpdateAsync(IRequest<IPatch> request);
1079+
```
1080+
1081+
In the example of a request to update a user, the object provided as the value of the patch argument has these property values:
1082+
1083+
* ResourceIdentifier.Identifier: "54D382A4-2050-4C03-94D1-E769F1D15682"
1084+
* ResourceIdentifier.SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
1085+
* (PatchRequest as PatchRequest2).Operations.Count: 1
1086+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).OperationName: OperationName.Add
1087+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Path.AttributePath: "manager"
1088+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Value.Count: 1
1089+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Value.ElementAt(0).Reference: http://.../scim/Users/2819c223-7f76-453a-919d-413861904646
1090+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Value.ElementAt(0).Value: 2819c223-7f76-453a-919d-413861904646
1091+
1092+
***Example 6. Deprovision a user***
1093+
1094+
To deprovision a user from an identity store fronted by an SCIM service, Azure AD sends a request such as:
1095+
1096+
```
1097+
DELETE ~/scim/Users/54D382A4-2050-4C03-94D1-E769F1D15682 HTTP/1.1
1098+
Authorization: Bearer ...
1099+
```
1100+
1101+
In the sample code the request is translated into a call to the DeleteAsync method of the services provider. Here is the signature of that method:
1102+
1103+
```csharp
1104+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
1105+
// Microsoft.SCIM.IRequest is defined in
1106+
// Microsoft.SCIM.Service.
1107+
// Microsoft.SCIM.IResourceIdentifier,
1108+
// is defined in Microsoft.SCIM.Protocol.
1109+
1110+
Task DeleteAsync(IRequest<IResourceIdentifier> request);
1111+
```
1112+
1113+
The object provided as the value of the resourceIdentifier argument has these property values in the example of a request to deprovision a user:
1114+
1115+
* ResourceIdentifier.Identifier: "54D382A4-2050-4C03-94D1-E769F1D15682"
1116+
* ResourceIdentifier.SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
7611117

7621118
## Step 4: Integrate your SCIM endpoint with the Azure AD SCIM client
7631119

0 commit comments

Comments
 (0)