This repository has been archived by the owner on Dec 13, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4k
/
IdentityServerAuthenticationService.cs
174 lines (146 loc) · 7.83 KB
/
IdentityServerAuthenticationService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer4.Services;
using Microsoft.Extensions.Logging;
using IdentityServer4.Configuration.DependencyInjection;
using IdentityServer4.Extensions;
using System;
using IdentityModel;
using System.Linq;
namespace IdentityServer4.Hosting
{
// this decorates the real authentication service to detect when the
// user is being signed in. this allows us to ensure the user has
// the claims needed for identity server to do its job. it also allows
// us to track signin/signout so we can issue/remove the session id
// cookie used for check session iframe for session management spec.
// finally, we track if signout is called to collaborate with the
// FederatedSignoutAuthenticationHandlerProvider for federated signout.
internal class IdentityServerAuthenticationService : IAuthenticationService
{
private readonly IAuthenticationService _inner;
private readonly IAuthenticationSchemeProvider _schemes;
private readonly ISystemClock _clock;
private readonly IUserSession _session;
private readonly ILogger<IdentityServerAuthenticationService> _logger;
public IdentityServerAuthenticationService(
Decorator<IAuthenticationService> decorator,
IAuthenticationSchemeProvider schemes,
ISystemClock clock,
IUserSession session,
ILogger<IdentityServerAuthenticationService> logger)
{
_inner = decorator.Instance;
_schemes = schemes;
_clock = clock;
_session = session;
_logger = logger;
}
private async Task<string> GetCookieAuthenticationSchemeAsync()
{
var scheme = await _schemes.GetDefaultAuthenticateSchemeAsync();
if (scheme == null)
{
throw new InvalidOperationException("No DefaultAuthenticateScheme found.");
}
return scheme.Name;
}
public async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
{
var defaultScheme = await _schemes.GetDefaultSignInSchemeAsync();
var cookieScheme = await GetCookieAuthenticationSchemeAsync();
if ((scheme == null && defaultScheme?.Name == cookieScheme) || scheme == cookieScheme)
{
AugmentPrincipal(principal);
if (properties == null) properties = new AuthenticationProperties();
await _session.CreateSessionIdAsync(principal, properties);
}
await _inner.SignInAsync(context, scheme, principal, properties);
}
private void AugmentPrincipal(ClaimsPrincipal principal)
{
_logger.LogDebug("Augmenting SignInContext");
AssertRequiredClaims(principal);
AugmentMissingClaims(principal, _clock.UtcNow.UtcDateTime);
}
public async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
{
var defaultScheme = await _schemes.GetDefaultSignOutSchemeAsync();
var cookieScheme = await GetCookieAuthenticationSchemeAsync();
if ((scheme == null && defaultScheme?.Name == cookieScheme) || scheme == cookieScheme)
{
// this sets a flag used by the FederatedSignoutAuthenticationHandlerProvider
context.SetSignOutCalled();
// this clears our session id cookie so JS clients can detect the user has signed out
await _session.RemoveSessionIdCookieAsync();
}
await _inner.SignOutAsync(context, scheme, properties);
}
public Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
{
return _inner.AuthenticateAsync(context, scheme);
}
public Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
{
return _inner.ChallengeAsync(context, scheme, properties);
}
public Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
{
return _inner.ForbidAsync(context, scheme, properties);
}
private void AssertRequiredClaims(ClaimsPrincipal principal)
{
// for now, we don't allow more than one identity in the principal/cookie
if (principal.Identities.Count() != 1) throw new InvalidOperationException("only a single identity supported");
if (principal.FindFirst(JwtClaimTypes.Subject) == null) throw new InvalidOperationException("sub claim is missing");
}
private void AugmentMissingClaims(ClaimsPrincipal principal, DateTime authTime)
{
var identity = principal.Identities.First();
// ASP.NET Identity issues this claim type and uses the authentication middleware name
// such as "Google" for the value. this code is trying to correct/convert that for
// our scenario. IOW, we take their old AuthenticationMethod value of "Google"
// and issue it as the idp claim. we then also issue a amr with "external"
var amr = identity.FindFirst(ClaimTypes.AuthenticationMethod);
if (amr != null &&
identity.FindFirst(JwtClaimTypes.IdentityProvider) == null &&
identity.FindFirst(JwtClaimTypes.AuthenticationMethod) == null)
{
_logger.LogDebug("Removing amr claim with value: {value}", amr.Value);
identity.RemoveClaim(amr);
_logger.LogDebug("Adding idp claim with value: {value}", amr.Value);
identity.AddClaim(new Claim(JwtClaimTypes.IdentityProvider, amr.Value));
_logger.LogDebug("Adding amr claim with value: {value}", Constants.ExternalAuthenticationMethod);
identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationMethod, Constants.ExternalAuthenticationMethod));
}
if (identity.FindFirst(JwtClaimTypes.IdentityProvider) == null)
{
_logger.LogDebug("Adding idp claim with value: {value}", IdentityServerConstants.LocalIdentityProvider);
identity.AddClaim(new Claim(JwtClaimTypes.IdentityProvider, IdentityServerConstants.LocalIdentityProvider));
}
if (identity.FindFirst(JwtClaimTypes.AuthenticationMethod) == null)
{
if (identity.FindFirst(JwtClaimTypes.IdentityProvider).Value == IdentityServerConstants.LocalIdentityProvider)
{
_logger.LogDebug("Adding amr claim with value: {value}", OidcConstants.AuthenticationMethods.Password);
identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationMethod, OidcConstants.AuthenticationMethods.Password));
}
else
{
_logger.LogDebug("Adding amr claim with value: {value}", Constants.ExternalAuthenticationMethod);
identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationMethod, Constants.ExternalAuthenticationMethod));
}
}
if (identity.FindFirst(JwtClaimTypes.AuthenticationTime) == null)
{
var time = authTime.ToEpochTime().ToString();
_logger.LogDebug("Adding auth_time claim with value: {value}", time);
identity.AddClaim(new Claim(JwtClaimTypes.AuthenticationTime, time, ClaimValueTypes.Integer));
}
}
}
}