3
3
4
4
5
5
using IdentityModel ;
6
+ using IdentityServer4 . Models ;
7
+ using IdentityServer4 . Quickstart . UI . Filters ;
6
8
using IdentityServer4 . Quickstart . UI . Models ;
9
+ using IdentityServer4 . Quickstart . UI . Users ;
7
10
using IdentityServer4 . Services ;
8
- using IdentityServer4 . Services . InMemory ;
11
+ using IdentityServer4 . Stores ;
9
12
using Microsoft . AspNetCore . Http . Authentication ;
10
13
using Microsoft . AspNetCore . Mvc ;
11
14
using System ;
12
15
using System . Collections . Generic ;
13
16
using System . Linq ;
14
17
using System . Security . Claims ;
15
- using System . Text . Encodings . Web ;
18
+ using System . Security . Principal ;
16
19
using System . Threading . Tasks ;
17
- using IdentityServer4 . Models ;
18
- using IdentityServer4 . Stores ;
19
- using Host . Filters ;
20
20
21
21
namespace IdentityServer4 . Quickstart . UI . Controllers
22
22
{
@@ -28,16 +28,20 @@ namespace IdentityServer4.Quickstart.UI.Controllers
28
28
[ SecurityHeaders ]
29
29
public class AccountController : Controller
30
30
{
31
- private readonly InMemoryUserLoginService _loginService ;
31
+ private readonly TestUserStore _users ;
32
+
32
33
private readonly IIdentityServerInteractionService _interaction ;
33
34
private readonly IClientStore _clientStore ;
34
35
36
+ // if you want to support Windows authentication, specify the scheme you want to use
37
+ private readonly string _windowsAuthenticationScheme = "Negotiate" ;
38
+
35
39
public AccountController (
36
- InMemoryUserLoginService loginService ,
40
+ TestUserStore users ,
37
41
IIdentityServerInteractionService interaction ,
38
42
IClientStore clientStore )
39
43
{
40
- _loginService = loginService ;
44
+ _users = users ;
41
45
_interaction = interaction ;
42
46
_clientStore = clientStore ;
43
47
}
@@ -52,15 +56,15 @@ public async Task<IActionResult> Login(string returnUrl)
52
56
if ( context ? . IdP != null )
53
57
{
54
58
// if IdP is passed, then bypass showing the login screen
55
- return ExternalLogin ( context . IdP , returnUrl ) ;
59
+ return await ExternalLogin ( context . IdP , returnUrl ) ;
56
60
}
57
61
58
62
var vm = await BuildLoginViewModelAsync ( returnUrl , context ) ;
59
63
60
64
if ( vm . EnableLocalLogin == false && vm . ExternalProviders . Count ( ) == 1 )
61
65
{
62
66
// only one option for logging in
63
- return ExternalLogin ( vm . ExternalProviders . First ( ) . AuthenticationScheme , returnUrl ) ;
67
+ return await ExternalLogin ( vm . ExternalProviders . First ( ) . AuthenticationScheme , returnUrl ) ;
64
68
}
65
69
66
70
return View ( vm ) ;
@@ -76,10 +80,10 @@ public async Task<IActionResult> Login(LoginInputModel model)
76
80
if ( ModelState . IsValid )
77
81
{
78
82
// validate username/password against in-memory store
79
- if ( _loginService . ValidateCredentials ( model . Username , model . Password ) )
83
+ if ( _users . ValidateCredentials ( model . Username , model . Password ) )
80
84
{
81
85
// issue authentication cookie with subject ID and username
82
- var user = _loginService . FindByUsername ( model . Username ) ;
86
+ var user = _users . FindByUsername ( model . Username ) ;
83
87
84
88
AuthenticationProperties props = null ;
85
89
// only set explicit expiration here if persistent.
@@ -93,7 +97,7 @@ public async Task<IActionResult> Login(LoginInputModel model)
93
97
} ;
94
98
} ;
95
99
96
- await HttpContext . Authentication . SignInAsync ( user . Subject , user . Username , props ) ;
100
+ await HttpContext . Authentication . SignInAsync ( user . SubjectId , user . Username , props ) ;
97
101
98
102
// make sure the returnUrl is still valid, and if yes - redirect back to authorize endpoint
99
103
if ( _interaction . IsValidReturnUrl ( model . ReturnUrl ) )
@@ -114,13 +118,26 @@ public async Task<IActionResult> Login(LoginInputModel model)
114
118
115
119
async Task < LoginViewModel > BuildLoginViewModelAsync ( string returnUrl , AuthorizationRequest context )
116
120
{
117
- var providers = HttpContext . Authentication . GetAuthenticationSchemes ( )
121
+ var schemes = HttpContext . Authentication . GetAuthenticationSchemes ( ) ;
122
+
123
+ var providers = schemes
118
124
. Where ( x => x . DisplayName != null )
119
125
. Select ( x => new ExternalProvider
120
126
{
121
127
DisplayName = x . DisplayName ,
122
128
AuthenticationScheme = x . AuthenticationScheme
129
+ } ) . ToList ( ) ;
130
+
131
+ // add Windows provider if present
132
+ var windows = schemes . FirstOrDefault ( s => s . AuthenticationScheme == _windowsAuthenticationScheme ) ;
133
+ if ( windows != null )
134
+ {
135
+ providers . Add ( new ExternalProvider
136
+ {
137
+ AuthenticationScheme = _windowsAuthenticationScheme ,
138
+ DisplayName = "Windows"
123
139
} ) ;
140
+ }
124
141
125
142
var allowLocal = true ;
126
143
if ( context ? . ClientId != null )
@@ -132,7 +149,7 @@ async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl, Authorizat
132
149
133
150
if ( client . IdentityProviderRestrictions != null && client . IdentityProviderRestrictions . Any ( ) )
134
151
{
135
- providers = providers . Where ( provider => client . IdentityProviderRestrictions . Contains ( provider . AuthenticationScheme ) ) ;
152
+ providers = providers . Where ( provider => client . IdentityProviderRestrictions . Contains ( provider . AuthenticationScheme ) ) . ToList ( ) ;
136
153
}
137
154
}
138
155
}
@@ -202,13 +219,16 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
202
219
model . LogoutId = await _interaction . CreateLogoutContextAsync ( ) ;
203
220
}
204
221
205
- string url = "/Account/ Logout?logoutId=" + model . LogoutId ;
222
+ string url = Url . Action ( " Logout" , new { logoutId = model . LogoutId } ) ;
206
223
try
207
224
{
208
225
// hack: try/catch to handle social providers that throw
209
226
await HttpContext . Authentication . SignOutAsync ( idp , new AuthenticationProperties { RedirectUri = url } ) ;
210
227
}
211
- catch ( NotSupportedException )
228
+ catch ( NotSupportedException ) // this is for the external providers that don't have signout
229
+ {
230
+ }
231
+ catch ( InvalidOperationException ) // this is for Windows/Negotiate
212
232
{
213
233
}
214
234
}
@@ -217,7 +237,7 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
217
237
await HttpContext . Authentication . SignOutAsync ( ) ;
218
238
219
239
// set this so UI rendering sees an anonymous user
220
- HttpContext . User = new ClaimsPrincipal ( new ClaimsIdentity ( ) ) ;
240
+ ViewData [ "signed-out" ] = true ;
221
241
222
242
// get context information (client name, post logout redirect URI and iframe for federated signout)
223
243
var logout = await _interaction . GetLogoutContextAsync ( model . LogoutId ) ;
@@ -236,21 +256,32 @@ public async Task<IActionResult> Logout(LogoutViewModel model)
236
256
/// initiate roundtrip to external authentication provider
237
257
/// </summary>
238
258
[ HttpGet ]
239
- public IActionResult ExternalLogin ( string provider , string returnUrl )
259
+ public async Task < IActionResult > ExternalLogin ( string provider , string returnUrl )
240
260
{
241
- if ( returnUrl != null )
261
+ returnUrl = Url . Action ( "ExternalLoginCallback" , new { returnUrl = returnUrl } ) ;
262
+
263
+ if ( provider == _windowsAuthenticationScheme && HttpContext . User is WindowsPrincipal )
242
264
{
243
- returnUrl = UrlEncoder . Default . Encode ( returnUrl ) ;
244
- }
245
- returnUrl = "/account/externallogincallback?returnUrl=" + returnUrl ;
265
+ var props = new AuthenticationProperties ( ) ;
266
+ props . Items . Add ( "scheme" , _windowsAuthenticationScheme ) ;
267
+
268
+ var id = new ClaimsIdentity ( provider ) ;
269
+ id . AddClaim ( new Claim ( ClaimTypes . NameIdentifier , HttpContext . User . Identity . Name ) ) ;
270
+ id . AddClaim ( new Claim ( ClaimTypes . Name , HttpContext . User . Identity . Name ) ) ;
246
271
247
- // start challenge and roundtrip the return URL
248
- var props = new AuthenticationProperties
272
+ await HttpContext . Authentication . SignInAsync ( IdentityServerConstants . ExternalCookieAuthenticationScheme , new ClaimsPrincipal ( id ) , props ) ;
273
+ return Redirect ( returnUrl ) ;
274
+ }
275
+ else
249
276
{
250
- RedirectUri = returnUrl ,
251
- Items = { { "scheme" , provider } }
252
- } ;
253
- return new ChallengeResult ( provider , props ) ;
277
+ // start challenge and roundtrip the return URL
278
+ var props = new AuthenticationProperties
279
+ {
280
+ RedirectUri = returnUrl ,
281
+ Items = { { "scheme" , provider } }
282
+ } ;
283
+ return new ChallengeResult ( provider , props ) ;
284
+ }
254
285
}
255
286
256
287
/// <summary>
@@ -289,12 +320,12 @@ public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
289
320
var userId = userIdClaim . Value ;
290
321
291
322
// check if the external user is already provisioned
292
- var user = _loginService . FindByExternalProvider ( provider , userId ) ;
323
+ var user = _users . FindByExternalProvider ( provider , userId ) ;
293
324
if ( user == null )
294
325
{
295
326
// this sample simply auto-provisions new external user
296
327
// another common approach is to start a registrations workflow first
297
- user = _loginService . AutoProvisionUser ( provider , userId , claims ) ;
328
+ user = _users . AutoProvisionUser ( provider , userId , claims ) ;
298
329
}
299
330
300
331
var additionalClaims = new List < Claim > ( ) ;
@@ -307,7 +338,7 @@ public async Task<IActionResult> ExternalLoginCallback(string returnUrl)
307
338
}
308
339
309
340
// issue authentication cookie for user
310
- await HttpContext . Authentication . SignInAsync ( user . Subject , user . Username , provider , additionalClaims . ToArray ( ) ) ;
341
+ await HttpContext . Authentication . SignInAsync ( user . SubjectId , user . Username , provider , additionalClaims . ToArray ( ) ) ;
311
342
312
343
// delete temporary cookie used during external authentication
313
344
await HttpContext . Authentication . SignOutAsync ( IdentityServerConstants . ExternalCookieAuthenticationScheme ) ;
0 commit comments