Skip to content

Commit cd5b182

Browse files
authored
Merge pull request #109140 from marcusca10/marcusca10-patch-1
Update Build a SCIM endpoint with Azure AD
2 parents 5b3c225 + f85c466 commit cd5b182

File tree

1 file changed

+364
-3
lines changed

1 file changed

+364
-3
lines changed

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

Lines changed: 364 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -754,10 +754,371 @@ 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 keeping classes in memory as a 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 SCIM service must have an HTTP address and server authentication certificate of which the root certification authority is one of the following names:
791+
792+
* CNNIC
793+
* Comodo
794+
* CyberTrust
795+
* DigiCert
796+
* GeoTrust
797+
* GlobalSign
798+
* Go Daddy
799+
* VeriSign
800+
* WoSign
801+
802+
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:
803+
804+
* Microsoft.SCIM.WebHostSample: https://localhost:5001
805+
* IIS Express: https://localhost:44359/
806+
807+
For more information on HTTPS in ASP.NET Core use the following link:
808+
[Enforce HTTPS in ASP.NET Core](https://docs.microsoft.com/aspnet/core/security/enforcing-ssl)
809+
810+
### Handling endpoint authentication
811+
812+
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.
813+
814+
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.
815+
816+
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]](mailto:[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_.
817+
818+
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:
819+
820+
```csharp
821+
public void ConfigureServices(IServiceCollection services)
822+
{
823+
if (_env.IsDevelopment())
824+
{
825+
...
826+
}
827+
else
828+
{
829+
services.AddAuthentication(options =>
830+
{
831+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
832+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
833+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
834+
})
835+
.AddJwtBearer(options =>
836+
{
837+
options.Authority = " https://sts.windows.net/cbb1a5ac-f33b-45fa-9bf5-f37db0fed422/";
838+
options.Audience = "8adf8e6e-67b2-4cf2-a259-e3dc5476c621";
839+
...
840+
});
841+
}
842+
...
843+
}
844+
845+
public void Configure(IApplicationBuilder app)
846+
{
847+
...
848+
app.UseAuthentication();
849+
app.UseAuthorization();
850+
...
851+
}
852+
```
853+
854+
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.
855+
856+
For more information on multiple environments in ASP.NET Core use the following link:
857+
[Use multiple environments in ASP.NET Core](
858+
https://docs.microsoft.com/aspnet/core/fundamentals/environments)
859+
860+
The following code enforces that requests to any of the service’s endpoints are authenticated using a bearer token signed with a custom key:
861+
862+
```csharp
863+
public void ConfigureServices(IServiceCollection services)
864+
{
865+
if (_env.IsDevelopment())
866+
{
867+
services.AddAuthentication(options =>
868+
{
869+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
870+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
871+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
872+
})
873+
.AddJwtBearer(options =>
874+
{
875+
options.TokenValidationParameters =
876+
new TokenValidationParameters
877+
{
878+
ValidateIssuer = false,
879+
ValidateAudience = false,
880+
ValidateLifetime = false,
881+
ValidateIssuerSigningKey = false,
882+
ValidIssuer = "Microsoft.Security.Bearer",
883+
ValidAudience = "Microsoft.Security.Bearer",
884+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A1B2C3D4E5F6A1B2C3D4E5F6"))
885+
};
886+
});
887+
}
888+
...
889+
```
890+
891+
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:
892+
893+
```csharp
894+
private string GenerateJSONWebToken()
895+
{
896+
// Create token key
897+
SymmetricSecurityKey securityKey =
898+
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A1B2C3D4E5F6A1B2C3D4E5F6"));
899+
SigningCredentials credentials =
900+
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
901+
902+
// Set token expiration
903+
DateTime startTime = DateTime.UtcNow;
904+
DateTime expiryTime = startTime.AddMinutes(120);
905+
906+
// Generate the token
907+
JwtSecurityToken token =
908+
new JwtSecurityToken(
909+
"Microsoft.Security.Bearer",
910+
"Microsoft.Security.Bearer",
911+
null,
912+
notBefore: startTime,
913+
expires: expiryTime,
914+
signingCredentials: credentials);
915+
916+
string result = new JwtSecurityTokenHandler().WriteToken(token);
917+
return result;
918+
}
919+
```
920+
921+
### Handling provisioning and deprovisioning of users
922+
923+
***Example 1. Query the service for a matching user***
924+
925+
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.
926+
927+
>[!NOTE]
928+
> 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).
929+
930+
```
931+
GET https://.../scim/Users?filter=externalId eq jyoung HTTP/1.1
932+
Authorization: Bearer ...
933+
```
934+
935+
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:
936+
937+
```csharp
938+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
939+
// Microsoft.SCIM.IRequest is defined in
940+
// Microsoft.SCIM.Service.
941+
// Microsoft.SCIM.Resource is defined in
942+
// Microsoft.SCIM.Schemas.
943+
// Microsoft.SCIM.IQueryParameters is defined in
944+
// Microsoft.SCIM.Protocol.
945+
946+
Task<Resource[]> QueryAsync(IRequest<IQueryParameters> request);
947+
```
948+
949+
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:
950+
951+
* parameters.AlternateFilters.Count: 1
952+
* parameters.AlternateFilters.ElementAt(0).AttributePath: "externalId"
953+
* parameters.AlternateFilters.ElementAt(0).ComparisonOperator: ComparisonOperator.Equals
954+
* parameters.AlternateFilter.ElementAt(0).ComparisonValue: "jyoung"
955+
956+
***Example 2. Provision a user***
957+
958+
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:
959+
960+
```
961+
POST https://.../scim/Users HTTP/1.1
962+
Authorization: Bearer ...
963+
Content-type: application/scim+json
964+
{
965+
"schemas":
966+
[
967+
"urn:ietf:params:scim:schemas:core:2.0:User",
968+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0User"],
969+
"externalId":"jyoung",
970+
"userName":"jyoung",
971+
"active":true,
972+
"addresses":null,
973+
"displayName":"Joy Young",
974+
"emails": [
975+
{
976+
"type":"work",
977+
"value":"[email protected]",
978+
"primary":true}],
979+
"meta": {
980+
"resourceType":"User"},
981+
"name":{
982+
"familyName":"Young",
983+
"givenName":"Joy"},
984+
"phoneNumbers":null,
985+
"preferredLanguage":null,
986+
"title":null,
987+
"department":null,
988+
"manager":null}
989+
```
990+
991+
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:
992+
993+
```csharp
994+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
995+
// Microsoft.SCIM.IRequest is defined in
996+
// Microsoft.SCIM.Service.
997+
// Microsoft.SCIM.Resource is defined in
998+
// Microsoft.SCIM.Schemas.
999+
1000+
Task<Resource> CreateAsync(IRequest<Resource> request);
1001+
```
1002+
1003+
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.
1004+
1005+
***Example 3. Query the current state of a user***
1006+
1007+
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:
1008+
1009+
```
1010+
GET ~/scim/Users/54D382A4-2050-4C03-94D1-E769F1D15682 HTTP/1.1
1011+
Authorization: Bearer ...
1012+
```
1013+
1014+
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:
1015+
1016+
```csharp
1017+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
1018+
// Microsoft.SCIM.IRequest is defined in
1019+
// Microsoft.SCIM.Service.
1020+
// Microsoft.SCIM.Resource and
1021+
// Microsoft.SCIM.IResourceRetrievalParameters
1022+
// are defined in Microsoft.SCIM.Schemas
1023+
1024+
Task<Resource> RetrieveAsync(IRequest<IResourceRetrievalParameters> request);
1025+
```
1026+
1027+
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:
1028+
1029+
* Identifier: "54D382A4-2050-4C03-94D1-E769F1D15682"
1030+
* SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
1031+
1032+
***Example 4. Query the value of a reference attribute to be updated***
1033+
1034+
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:
1035+
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:
1036+
1037+
* parameters.AlternateFilters.Count: 2
1038+
* parameters.AlternateFilters.ElementAt(x).AttributePath: "ID"
1039+
* parameters.AlternateFilters.ElementAt(x).ComparisonOperator: ComparisonOperator.Equals
1040+
* parameters.AlternateFilter.ElementAt(x).ComparisonValue: "54D382A4-2050-4C03-94D1-E769F1D15682"
1041+
* parameters.AlternateFilters.ElementAt(y).AttributePath: "manager"
1042+
* parameters.AlternateFilters.ElementAt(y).ComparisonOperator: ComparisonOperator.Equals
1043+
* parameters.AlternateFilter.ElementAt(y).ComparisonValue: "2819c223-7f76-453a-919d-413861904646"
1044+
* parameters.RequestedAttributePaths.ElementAt(0): "ID"
1045+
* parameters.SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
1046+
1047+
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.
1048+
1049+
***Example 5. Request from Azure AD to an SCIM service to update a user***
1050+
1051+
Here is an example of a request from Azure Active Directory to an SCIM service to update a user:
1052+
1053+
```
1054+
PATCH ~/scim/Users/54D382A4-2050-4C03-94D1-E769F1D15682 HTTP/1.1
1055+
Authorization: Bearer ...
1056+
Content-type: application/scim+json
1057+
{
1058+
"schemas":
1059+
[
1060+
"urn:ietf:params:scim:api:messages:2.0:PatchOp"],
1061+
"Operations":
1062+
[
1063+
{
1064+
"op":"Add",
1065+
"path":"manager",
1066+
"value":
1067+
[
1068+
{
1069+
"$ref":"http://.../scim/Users/2819c223-7f76-453a-919d-413861904646",
1070+
"value":"2819c223-7f76-453a-919d-413861904646"}]}]}
1071+
```
1072+
1073+
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:
1074+
1075+
```csharp
1076+
// System.Threading.Tasks.Tasks and
1077+
// System.Collections.Generic.IReadOnlyCollection<T> // are defined in mscorlib.dll.
1078+
// Microsoft.SCIM.IRequest is defined in
1079+
// Microsoft.SCIM.Service.
1080+
// Microsoft.SCIM.IPatch,
1081+
// is defined in Microsoft.SCIM.Protocol.
1082+
1083+
Task UpdateAsync(IRequest<IPatch> request);
1084+
```
1085+
1086+
In the example of a request to update a user, the object provided as the value of the patch argument has these property values:
1087+
1088+
* ResourceIdentifier.Identifier: "54D382A4-2050-4C03-94D1-E769F1D15682"
1089+
* ResourceIdentifier.SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
1090+
* (PatchRequest as PatchRequest2).Operations.Count: 1
1091+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).OperationName: OperationName.Add
1092+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Path.AttributePath: "manager"
1093+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Value.Count: 1
1094+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Value.ElementAt(0).Reference: http://.../scim/Users/2819c223-7f76-453a-919d-413861904646
1095+
* (PatchRequest as PatchRequest2).Operations.ElementAt(0).Value.ElementAt(0).Value: 2819c223-7f76-453a-919d-413861904646
1096+
1097+
***Example 6. Deprovision a user***
1098+
1099+
To deprovision a user from an identity store fronted by an SCIM service, Azure AD sends a request such as:
1100+
1101+
```
1102+
DELETE ~/scim/Users/54D382A4-2050-4C03-94D1-E769F1D15682 HTTP/1.1
1103+
Authorization: Bearer ...
1104+
```
1105+
1106+
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:
1107+
1108+
```csharp
1109+
// System.Threading.Tasks.Tasks is defined in mscorlib.dll.
1110+
// Microsoft.SCIM.IRequest is defined in
1111+
// Microsoft.SCIM.Service.
1112+
// Microsoft.SCIM.IResourceIdentifier,
1113+
// is defined in Microsoft.SCIM.Protocol.
1114+
1115+
Task DeleteAsync(IRequest<IResourceIdentifier> request);
1116+
```
1117+
1118+
The object provided as the value of the resourceIdentifier argument has these property values in the example of a request to deprovision a user:
1119+
1120+
* ResourceIdentifier.Identifier: "54D382A4-2050-4C03-94D1-E769F1D15682"
1121+
* ResourceIdentifier.SchemaIdentifier: "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
7611122

7621123
## Step 4: Integrate your SCIM endpoint with the Azure AD SCIM client
7631124

0 commit comments

Comments
 (0)