|
22 | 22 |
|
23 | 23 | namespace AspNet.Security.OAuth.Introspection {
|
24 | 24 | public class OAuthIntrospectionHandler : AuthenticationHandler<OAuthIntrospectionOptions> {
|
25 |
| - protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { |
26 |
| - try { |
27 |
| - // Give application opportunity to find from a different location, adjust, or reject token |
28 |
| - var accessTokenReceivedContext = new AccessTokenReceivedContext(Context, Options); |
29 |
| - |
30 |
| - // event can set the token |
31 |
| - await Options.Events.AccessTokenReceived(accessTokenReceivedContext); |
32 |
| - if (accessTokenReceivedContext.HandledResponse) { |
33 |
| - return AuthenticateResult.Success(accessTokenReceivedContext.Ticket); |
34 |
| - } |
35 |
| - if (accessTokenReceivedContext.Skipped) { |
36 |
| - Logger.LogInformation("Authentication was skipped by event processing."); |
37 |
| - |
38 |
| - return AuthenticateResult.Skip(); |
39 |
| - } |
| 25 | + protected override async Task<AuthenticateResult> HandleAuthenticateAsync() |
| 26 | + { |
| 27 | + // Give application opportunity to find from a different location, adjust, or reject token |
| 28 | + var accessTokenReceivedContext = new AccessTokenReceivedContext(Context, Options); |
| 29 | + |
| 30 | + // event can set the token |
| 31 | + await Options.Events.AccessTokenReceived(accessTokenReceivedContext); |
| 32 | + if (accessTokenReceivedContext.HandledResponse) |
| 33 | + { |
| 34 | + return AuthenticateResult.Success(accessTokenReceivedContext.Ticket); |
| 35 | + } |
40 | 36 |
|
41 |
| - // If application retrieved token from somewhere else, use that. |
42 |
| - string token = accessTokenReceivedContext.Token; |
| 37 | + if (accessTokenReceivedContext.Skipped) |
| 38 | + { |
| 39 | + Logger.LogInformation("Authentication was skipped by event processing."); |
43 | 40 |
|
44 |
| - if (string.IsNullOrWhiteSpace(token)) { |
45 |
| - string header = Request.Headers[HeaderNames.Authorization]; |
46 |
| - if (string.IsNullOrEmpty(header)) { |
47 |
| - return AuthenticateResult.Fail("Authentication failed because the bearer token " + |
48 |
| - "was missing from the 'Authorization' header."); |
49 |
| - } |
| 41 | + return AuthenticateResult.Skip(); |
| 42 | + } |
50 | 43 |
|
51 |
| - // Ensure that the authorization header contains the mandatory "Bearer" scheme. |
52 |
| - // See https://tools.ietf.org/html/rfc6750#section-2.1 |
53 |
| - if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { |
54 |
| - return AuthenticateResult.Fail("Authentication failed because an invalid scheme " + |
55 |
| - "was used in the 'Authorization' header."); |
56 |
| - } |
| 44 | + // If application retrieved token from somewhere else, use that. |
| 45 | + string token = accessTokenReceivedContext.Token; |
57 | 46 |
|
58 |
| - token = header.Substring("Bearer ".Length); |
59 |
| - } |
60 |
| - if (string.IsNullOrWhiteSpace(token)) { |
| 47 | + if (string.IsNullOrWhiteSpace(token)) |
| 48 | + { |
| 49 | + string header = Request.Headers[HeaderNames.Authorization]; |
| 50 | + if (string.IsNullOrEmpty(header)) |
| 51 | + { |
61 | 52 | return AuthenticateResult.Fail("Authentication failed because the bearer token " +
|
62 | 53 | "was missing from the 'Authorization' header.");
|
63 | 54 | }
|
64 | 55 |
|
65 |
| - // Try to resolve the authentication ticket from the distributed cache. If none |
66 |
| - // can be found, a new introspection request is sent to the authorization server. |
67 |
| - var ticket = await RetrieveTicketAsync(token); |
68 |
| - if (ticket == null) { |
69 |
| - JObject payload; |
70 |
| - // Allow interception of the introspection retrieval process via events |
71 |
| - var requestTokenIntrospectionContext = new RequestTokenIntrospectionContext(Context, Options, token); |
72 |
| - await Options.Events.RequestTokenIntrospection(requestTokenIntrospectionContext); |
73 |
| - if (requestTokenIntrospectionContext.HandledResponse) { |
74 |
| - return AuthenticateResult.Success(requestTokenIntrospectionContext.Ticket); |
75 |
| - } |
76 |
| - else if (requestTokenIntrospectionContext.Skipped) { |
77 |
| - return AuthenticateResult.Skip(); |
78 |
| - } |
79 |
| - else { |
80 |
| - // Return a failed authentication result if the introspection |
81 |
| - // request failed or if the "active" claim was false. |
82 |
| - payload = requestTokenIntrospectionContext.Payload ?? await GetIntrospectionPayloadAsync(token); |
83 |
| - } |
| 56 | + // Ensure that the authorization header contains the mandatory "Bearer" scheme. |
| 57 | + // See https://tools.ietf.org/html/rfc6750#section-2.1 |
| 58 | + if (!header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) |
| 59 | + { |
| 60 | + return AuthenticateResult.Fail("Authentication failed because an invalid scheme " + |
| 61 | + "was used in the 'Authorization' header."); |
| 62 | + } |
84 | 63 |
|
85 |
| - if (payload == null || !payload.Value<bool>(OAuthIntrospectionConstants.Claims.Active)) { |
86 |
| - return AuthenticateResult.Fail("Authentication failed because the authorization " + |
87 |
| - "server rejected the access token."); |
88 |
| - } |
| 64 | + token = header.Substring("Bearer ".Length); |
| 65 | + } |
| 66 | + if (string.IsNullOrWhiteSpace(token)) |
| 67 | + { |
| 68 | + return AuthenticateResult.Fail("Authentication failed because the bearer token " + |
| 69 | + "was missing from the 'Authorization' header."); |
| 70 | + } |
89 | 71 |
|
90 |
| - // Ensure that the access token was issued |
91 |
| - // to be used with this resource server. |
92 |
| - if (!await ValidateAudienceAsync(payload)) { |
93 |
| - return AuthenticateResult.Fail("Authentication failed because the access token " + |
94 |
| - "was not valid for this resource server."); |
95 |
| - } |
| 72 | + // Try to resolve the authentication ticket from the distributed cache. If none |
| 73 | + // can be found, a new introspection request is sent to the authorization server. |
| 74 | + var ticket = await RetrieveTicketAsync(token); |
| 75 | + if (ticket == null) |
| 76 | + { |
| 77 | + JObject payload; |
| 78 | + // Allow interception of the introspection retrieval process via events |
| 79 | + var requestTokenIntrospectionContext = new RequestTokenIntrospectionContext(Context, Options, token); |
96 | 80 |
|
97 |
| - // Allow interception of the ticket creation process via events |
98 |
| - var createTicketContext = new CreateTicketContext(Context, Options, payload); |
99 |
| - await Options.Events.CreateTicket(createTicketContext); |
100 |
| - if (createTicketContext.HandledResponse) { |
101 |
| - return AuthenticateResult.Success(createTicketContext.Ticket); |
102 |
| - } |
103 |
| - if (createTicketContext.Skipped) { |
104 |
| - return AuthenticateResult.Skip(); |
105 |
| - } |
| 81 | + await Options.Events.RequestTokenIntrospection(requestTokenIntrospectionContext); |
| 82 | + |
| 83 | + if (requestTokenIntrospectionContext.HandledResponse) |
| 84 | + { |
| 85 | + return AuthenticateResult.Success(requestTokenIntrospectionContext.Ticket); |
| 86 | + } |
106 | 87 |
|
107 |
| - // Create a new authentication ticket from the introspection |
108 |
| - // response returned by the authorization server. |
109 |
| - ticket = createTicketContext.Ticket ?? await CreateTicketAsync(payload); |
110 |
| - Debug.Assert(ticket != null); |
| 88 | + else if (requestTokenIntrospectionContext.Skipped) |
| 89 | + { |
| 90 | + return AuthenticateResult.Skip(); |
| 91 | + } |
111 | 92 |
|
112 |
| - await StoreTicketAsync(token, ticket); |
| 93 | + else { |
| 94 | + // Return a failed authentication result if the introspection |
| 95 | + // request failed or if the "active" claim was false. |
| 96 | + payload = requestTokenIntrospectionContext.Payload ?? await GetIntrospectionPayloadAsync(token); |
113 | 97 | }
|
114 | 98 |
|
115 |
| - // Allow for interception and handling of the token validated event. |
116 |
| - var tokenValidatedContext = new TokenValidatedContext(Context, Options, ticket); |
117 |
| - await Options.Events.TokenValidated(tokenValidatedContext); |
118 |
| - if (tokenValidatedContext.HandledResponse) |
| 99 | + if (payload == null || !payload.Value<bool>(OAuthIntrospectionConstants.Claims.Active)) |
119 | 100 | {
|
120 |
| - return AuthenticateResult.Success(tokenValidatedContext.Ticket); |
| 101 | + return AuthenticateResult.Fail("Authentication failed because the authorization " + |
| 102 | + "server rejected the access token."); |
121 | 103 | }
|
122 |
| - if (tokenValidatedContext.Skipped) |
| 104 | + |
| 105 | + // Ensure that the access token was issued |
| 106 | + // to be used with this resource server. |
| 107 | + if (!await ValidateAudienceAsync(payload)) |
123 | 108 | {
|
124 |
| - Logger.LogInformation("Authentication was skipped by event processing."); |
| 109 | + return AuthenticateResult.Fail("Authentication failed because the access token " + |
| 110 | + "was not valid for this resource server."); |
| 111 | + } |
125 | 112 |
|
| 113 | + // Allow interception of the ticket creation process via events |
| 114 | + var createTicketContext = new CreateTicketContext(Context, Options, payload); |
| 115 | + await Options.Events.CreateTicket(createTicketContext); |
| 116 | + if (createTicketContext.HandledResponse) |
| 117 | + { |
| 118 | + return AuthenticateResult.Success(createTicketContext.Ticket); |
| 119 | + } |
| 120 | + if (createTicketContext.Skipped) |
| 121 | + { |
126 | 122 | return AuthenticateResult.Skip();
|
127 | 123 | }
|
128 | 124 |
|
129 |
| - // Flow the ticket changes |
130 |
| - ticket = tokenValidatedContext.Ticket; |
| 125 | + // Create a new authentication ticket from the introspection |
| 126 | + // response returned by the authorization server. |
| 127 | + ticket = createTicketContext.Ticket ?? await CreateTicketAsync(payload); |
| 128 | + Debug.Assert(ticket != null); |
131 | 129 |
|
132 |
| - // Ensure that the authentication ticket is still valid. |
133 |
| - if (ticket.Properties.ExpiresUtc.HasValue && |
134 |
| - ticket.Properties.ExpiresUtc.Value < Options.SystemClock.UtcNow) { |
135 |
| - return AuthenticateResult.Fail("Authentication failed because the access token was expired."); |
136 |
| - } |
| 130 | + await StoreTicketAsync(token, ticket); |
| 131 | + } |
137 | 132 |
|
138 |
| - return AuthenticateResult.Success(ticket); |
| 133 | + // Allow for interception and handling of the token validated event. |
| 134 | + var tokenValidatedContext = new TokenValidatedContext(Context, Options, ticket); |
| 135 | + await Options.Events.TokenValidated(tokenValidatedContext); |
| 136 | + if (tokenValidatedContext.HandledResponse) |
| 137 | + { |
| 138 | + return AuthenticateResult.Success(tokenValidatedContext.Ticket); |
139 | 139 | }
|
140 |
| - catch (Exception exception) |
| 140 | + if (tokenValidatedContext.Skipped) |
141 | 141 | {
|
142 |
| - var authenticationFailedContext = new AuthenticationFailedContext(Context, Options) { |
143 |
| - Exception = exception |
144 |
| - }; |
| 142 | + Logger.LogInformation("Authentication was skipped by event processing."); |
145 | 143 |
|
146 |
| - await Options.Events.AuthenticationFailed(authenticationFailedContext); |
147 |
| - if (authenticationFailedContext.HandledResponse) { |
148 |
| - return AuthenticateResult.Success(authenticationFailedContext.Ticket); |
149 |
| - } |
| 144 | + return AuthenticateResult.Skip(); |
| 145 | + } |
150 | 146 |
|
151 |
| - if (authenticationFailedContext.Skipped) { |
152 |
| - return AuthenticateResult.Skip(); |
153 |
| - } |
| 147 | + // Flow the ticket changes |
| 148 | + ticket = tokenValidatedContext.Ticket; |
154 | 149 |
|
155 |
| - Logger.LogInformation("Exception occurred while processing message", authenticationFailedContext.Exception); |
156 |
| - throw authenticationFailedContext.Exception ?? exception; |
| 150 | + // Ensure that the authentication ticket is still valid. |
| 151 | + if (ticket.Properties.ExpiresUtc.HasValue && |
| 152 | + ticket.Properties.ExpiresUtc.Value < Options.SystemClock.UtcNow) |
| 153 | + { |
| 154 | + return AuthenticateResult.Fail("Authentication failed because the access token was expired."); |
157 | 155 | }
|
| 156 | + |
| 157 | + return AuthenticateResult.Success(ticket); |
158 | 158 | }
|
159 | 159 |
|
160 | 160 | protected virtual async Task<string> ResolveIntrospectionEndpointAsync(string issuer) {
|
|
0 commit comments