Skip to content

Commit 8f12451

Browse files
authored
Merge pull request #6 from AzureAD/security
Added token validation code for development and for publishing
2 parents a074698 + b115958 commit 8f12451

File tree

9 files changed

+203
-68
lines changed

9 files changed

+203
-68
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.Extensions.Configuration;
3+
using Microsoft.IdentityModel.Tokens;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IdentityModel.Tokens.Jwt;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Microsoft.SCIM.WebHostSample.Controllers
12+
{
13+
// Controller for generating a bearer token for authorization during testing.
14+
// This is not meant to replace proper Oauth for authentication purposes.
15+
[Route("scim/token")]
16+
[ApiController]
17+
public class TokenController : ControllerBase
18+
{
19+
private readonly IConfiguration _configuration;
20+
21+
private const int defaultTokenExpiration = 120;
22+
23+
public TokenController(IConfiguration Configuration)
24+
{
25+
_configuration = Configuration;
26+
}
27+
28+
private string GenerateJSONWebToken()
29+
{
30+
// Create token key
31+
SymmetricSecurityKey securityKey =
32+
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._configuration["Token:IssuerSigningKey"]));
33+
SigningCredentials credentials =
34+
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
35+
36+
// Set token expiration
37+
DateTime startTime = DateTime.UtcNow;
38+
DateTime expiryTime;
39+
double tokenExpiration;
40+
if (double.TryParse(this._configuration["Token:TokenLifetimeInMins"], out tokenExpiration))
41+
expiryTime = startTime.AddMinutes(tokenExpiration);
42+
else
43+
expiryTime = startTime.AddMinutes(defaultTokenExpiration);
44+
45+
// Generate the token
46+
JwtSecurityToken token =
47+
new JwtSecurityToken(
48+
this._configuration["Token:TokenIssuer"],
49+
this._configuration["Token:TokenAudience"],
50+
null,
51+
notBefore: startTime,
52+
expires: expiryTime,
53+
signingCredentials: credentials);
54+
55+
string result = new JwtSecurityTokenHandler().WriteToken(token);
56+
return result;
57+
}
58+
59+
[HttpGet]
60+
public ActionResult Get()
61+
{
62+
string tokenString = this.GenerateJSONWebToken();
63+
return this.Ok(new { token = tokenString });
64+
}
65+
66+
}
67+
}

Microsoft.SCIM.WebHostSample/Microsoft.SCIM.WebHostSample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
44
<TargetFramework>netcoreapp3.1</TargetFramework>

Microsoft.SCIM.WebHostSample/Startup.cs

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Hosting;
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.AspNetCore.Routing;
11+
using Microsoft.Extensions.Configuration;
1112
using Microsoft.Extensions.DependencyInjection;
1213
using Microsoft.Extensions.Hosting;
1314
using Microsoft.IdentityModel.Tokens;
@@ -17,11 +18,17 @@ namespace Microsoft.SCIM.WebHostSample
1718
{
1819
public class Startup
1920
{
21+
private readonly IWebHostEnvironment _env;
22+
private readonly IConfiguration _configuration;
23+
2024
public IMonitor MonitoringBehavior { get; set; }
2125
public IProvider ProviderBehavior { get; set; }
2226

23-
public Startup()
27+
public Startup(IWebHostEnvironment env, IConfiguration configuration)
2428
{
29+
this._env = env;
30+
this._configuration = configuration;
31+
2532
this.MonitoringBehavior = new ConsoleMonitor();
2633
this.ProviderBehavior = new InMemoryProvider();
2734
}
@@ -30,26 +37,62 @@ public Startup()
3037
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
3138
public void ConfigureServices(IServiceCollection services)
3239
{
33-
services.AddAuthentication(options =>
40+
if (_env.IsDevelopment())
3441
{
35-
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
36-
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
37-
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
38-
})
39-
.AddJwtBearer(options =>
42+
// Development environment code
43+
// Validation for bearer token for authorization used during testing.
44+
// NOTE: It's not recommended to use this code in production, it is not meant to replace proper OAuth authentication.
45+
// This option is primarily available for testing purposes.
46+
services.AddAuthentication(options =>
4047
{
41-
options.TokenValidationParameters =
42-
new TokenValidationParameters
48+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
49+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
50+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
51+
})
52+
.AddJwtBearer(options =>
53+
{
54+
options.TokenValidationParameters =
55+
new TokenValidationParameters
56+
{
57+
ValidateIssuer = false,
58+
ValidateAudience = false,
59+
ValidateLifetime = false,
60+
ValidateIssuerSigningKey = false,
61+
ValidIssuer = this._configuration["Token:TokenIssuer"],
62+
ValidAudience = this._configuration["Token:TokenAudience"],
63+
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(this._configuration["Token:IssuerSigningKey"]))
64+
};
65+
});
66+
}
67+
else
68+
{
69+
// Leave the optional Secret Token field blank
70+
// Azure AD includes an OAuth bearer token issued from Azure AD with each request
71+
// The following code validates the Azure AD-issued token
72+
// NOTE: It's not recommended to leave this field blank and rely on a token generated by Azure AD.
73+
// This option is primarily available for testing purposes.
74+
services.AddAuthentication(options =>
75+
{
76+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
77+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
78+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
79+
})
80+
.AddJwtBearer(options =>
81+
{
82+
options.Authority = this._configuration["Token:TokenIssuer"];
83+
options.Audience = this._configuration["Token:TokenAudience"];
84+
options.Events = new JwtBearerEvents
4385
{
44-
ValidateIssuer = false,
45-
ValidateAudience = false,
46-
ValidateLifetime = false,
47-
ValidateIssuerSigningKey = false,
48-
ValidIssuer = ServiceConstants.TokenIssuer,
49-
ValidAudience = ServiceConstants.TokenAudience,
50-
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ServiceConstants.TokenIssuer))
86+
OnTokenValidated = context =>
87+
{
88+
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.
89+
90+
return Task.CompletedTask;
91+
},
92+
OnAuthenticationFailed = AuthenticationFailed
5193
};
52-
});
94+
});
95+
}
5396

5497
services.AddControllers().AddNewtonsoftJson();
5598

@@ -58,9 +101,9 @@ public void ConfigureServices(IServiceCollection services)
58101
}
59102

60103
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
61-
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
104+
public void Configure(IApplicationBuilder app)
62105
{
63-
if (env.IsDevelopment())
106+
if (_env.IsDevelopment())
64107
{
65108
app.UseDeveloperExceptionPage();
66109
}
@@ -78,5 +121,16 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
78121
endpoints.MapDefaultControllerRoute();
79122
});
80123
}
124+
125+
private Task AuthenticationFailed(AuthenticationFailedContext arg)
126+
{
127+
// For debugging purposes only!
128+
var s = $"{{AuthenticationFailed: '{arg.Exception.Message}'}}";
129+
130+
arg.Response.ContentLength = s.Length;
131+
arg.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(s), 0, s.Length);
132+
133+
return Task.FromException(arg.Exception);
134+
}
81135
}
82136
}

Microsoft.SCIM.WebHostSample/appsettings.Development.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@
55
"Microsoft": "Warning",
66
"Microsoft.Hosting.Lifetime": "Information"
77
}
8+
},
9+
"Token": {
10+
"TokenAudience": "Microsoft.Security.Bearer",
11+
"TokenIssuer": "Microsoft.Security.Bearer",
12+
"IssuerSigningKey": "A1B2C3D4E5F6A1B2C3D4E5F6",
13+
"TokenLifetimeInMins": "120"
814
}
915
}

Microsoft.SCIM.WebHostSample/appsettings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
"Microsoft.Hosting.Lifetime": "Information"
77
}
88
},
9+
"Token": {
10+
"TokenAudience": "8adf8e6e-67b2-4cf2-a259-e3dc5476c621",
11+
"TokenIssuer": "https://sts.windows.net/<tenant_id>/"
12+
},
913
"AllowedHosts": "*"
1014
}

Microsoft.SystemForCrossDomainIdentityManagement/Service/Controllers/GroupsController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
namespace Microsoft.SCIM
66
{
77
using System;
8+
using Microsoft.AspNetCore.Authorization;
89
using Microsoft.AspNetCore.Mvc;
910

1011
[Route(ServiceConstants.RouteGroups)]
12+
[Authorize]
1113
[ApiController]
1214
public sealed class GroupsController : ControllerTemplate<Core2Group>
1315
{

Microsoft.SystemForCrossDomainIdentityManagement/Service/Controllers/TokenController.cs

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,48 +11,48 @@ namespace Microsoft.SCIM
1111
using Microsoft.AspNetCore.Mvc;
1212
using Microsoft.IdentityModel.Tokens;
1313

14-
// Controller for generating a bearer token for authorization during testing.
15-
// This is not meant to replace proper Oauth for authentication purposes.
16-
[Route(ServiceConstants.RouteToken)]
17-
[ApiController]
18-
public class KeyController : ControllerTemplate
19-
{
20-
private const int TokenLifetimeInMins = 120;
21-
22-
public KeyController(IProvider provider, IMonitor monitor)
23-
: base(provider, monitor)
24-
{
25-
}
26-
27-
private static string GenerateJSONWebToken()
28-
{
29-
SymmetricSecurityKey securityKey =
30-
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ServiceConstants.TokenIssuer));
31-
SigningCredentials credentials =
32-
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
33-
34-
DateTime startTime = DateTime.UtcNow;
35-
DateTime expiryTime = startTime.AddMinutes(KeyController.TokenLifetimeInMins);
36-
37-
JwtSecurityToken token =
38-
new JwtSecurityToken(
39-
ServiceConstants.TokenIssuer,
40-
ServiceConstants.TokenAudience,
41-
null,
42-
notBefore: startTime,
43-
expires: expiryTime,
44-
signingCredentials: credentials);
45-
46-
string result = new JwtSecurityTokenHandler().WriteToken(token);
47-
return result;
48-
}
49-
50-
[HttpGet]
51-
public ActionResult Get()
52-
{
53-
string tokenString = KeyController.GenerateJSONWebToken();
54-
return this.Ok(new { token = tokenString });
55-
}
56-
57-
}
14+
//// Controller for generating a bearer token for authorization during testing.
15+
//// This is not meant to replace proper Oauth for authentication purposes.
16+
//[Route(ServiceConstants.RouteToken)]
17+
//[ApiController]
18+
//public class KeyController : ControllerTemplate
19+
//{
20+
// private const int TokenLifetimeInMins = 120;
21+
22+
// public KeyController(IProvider provider, IMonitor monitor)
23+
// : base(provider, monitor)
24+
// {
25+
// }
26+
27+
// private static string GenerateJSONWebToken()
28+
// {
29+
// SymmetricSecurityKey securityKey =
30+
// new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ServiceConstants.TokenIssuer));
31+
// SigningCredentials credentials =
32+
// new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
33+
34+
// DateTime startTime = DateTime.UtcNow;
35+
// DateTime expiryTime = startTime.AddMinutes(KeyController.TokenLifetimeInMins);
36+
37+
// JwtSecurityToken token =
38+
// new JwtSecurityToken(
39+
// ServiceConstants.TokenIssuer,
40+
// ServiceConstants.TokenAudience,
41+
// null,
42+
// notBefore: startTime,
43+
// expires: expiryTime,
44+
// signingCredentials: credentials);
45+
46+
// string result = new JwtSecurityTokenHandler().WriteToken(token);
47+
// return result;
48+
// }
49+
50+
// [HttpGet]
51+
// public ActionResult Get()
52+
// {
53+
// string tokenString = KeyController.GenerateJSONWebToken();
54+
// return this.Ok(new { token = tokenString });
55+
// }
56+
57+
//}
5858
}

Microsoft.SystemForCrossDomainIdentityManagement/Service/Controllers/UsersController.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
namespace Microsoft.SCIM
66
{
77
using System;
8+
using Microsoft.AspNetCore.Authorization;
89
using Microsoft.AspNetCore.Mvc;
910

1011
[Route(ServiceConstants.RouteUsers)]
12+
[Authorize]
1113
[ApiController]
1214
public sealed class UsersController : ControllerTemplate<Core2EnterpriseUser>
1315
{

Microsoft.SystemForCrossDomainIdentityManagement/Service/ServiceConstants.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ namespace Microsoft.SCIM
77
public static class ServiceConstants
88
{
99
public const string PathSegmentResourceTypes = "ResourceTypes";
10-
public const string PathSegmentToken = "token";
10+
//public const string PathSegmentToken = "token";
1111
public const string PathSegmentSchemas = "Schemas";
1212
public const string PathSegmentServiceProviderConfiguration = "ServiceProviderConfig";
1313

1414
public const string RouteGroups = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ProtocolConstants.PathGroups;
1515
public const string RouteResourceTypes = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentResourceTypes;
1616
public const string RouteSchemas = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentSchemas;
1717
public const string RouteServiceConfiguration = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentServiceProviderConfiguration;
18-
public const string RouteToken = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentToken;
18+
//public const string RouteToken = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ServiceConstants.PathSegmentToken;
1919
public const string RouteUsers = SchemaConstants.PathInterface + ServiceConstants.SeparatorSegments + ProtocolConstants.PathUsers;
2020

2121
public const string SeparatorSegments = "/";
2222

23-
public const string TokenAudience = "Microsoft.Security.Bearer";
24-
public const string TokenIssuer = "Microsoft.Security.Bearer";
23+
//public const string TokenAudience = "Microsoft.Security.Bearer";
24+
//public const string TokenIssuer = "Microsoft.Security.Bearer";
2525
}
2626
}

0 commit comments

Comments
 (0)