Further improve error handling for OAuth providers
This commit is contained in:
parent
e9d2c53ebc
commit
238fdf24e8
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
|
||||||
}
|
}
|
||||||
|
|
||||||
// The refresh token is not available.
|
// The refresh token is not available.
|
||||||
return new OAuthTokenResponse(payload);
|
return OAuthTokenResponse.Success(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
|
protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Http.Authentication;
|
using Microsoft.AspNet.Http.Authentication;
|
||||||
|
|
@ -62,9 +63,14 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
||||||
|
|
||||||
var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
|
var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));
|
||||||
|
|
||||||
|
if (tokens.Error != null)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Failed(tokens.Error);
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(tokens.AccessToken))
|
if (string.IsNullOrEmpty(tokens.AccessToken))
|
||||||
{
|
{
|
||||||
return AuthenticateResult.Failed("Access token was not found.");
|
return AuthenticateResult.Failed("Failed to retrieve access token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var identity = new ClaimsIdentity(Options.ClaimsIssuer);
|
var identity = new ClaimsIdentity(Options.ClaimsIssuer);
|
||||||
|
|
@ -113,9 +119,25 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
||||||
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
requestMessage.Content = requestContent;
|
requestMessage.Content = requestContent;
|
||||||
var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
|
var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
|
||||||
response.EnsureSuccessStatusCode();
|
if (response.IsSuccessStatusCode)
|
||||||
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
|
{
|
||||||
return new OAuthTokenResponse(payload);
|
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
return OAuthTokenResponse.Success(payload);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var error = "OAuth token endpoint failure: " + await Display(response);
|
||||||
|
return OAuthTokenResponse.Failed(new Exception(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> Display(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
var output = new StringBuilder();
|
||||||
|
output.Append("Status: " + response.StatusCode + ";");
|
||||||
|
output.Append("Headers: " + response.Headers.ToString() + ";");
|
||||||
|
output.Append("Body: " + await response.Content.ReadAsStringAsync() + ";");
|
||||||
|
return output.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
|
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Authentication.OAuth
|
namespace Microsoft.AspNet.Authentication.OAuth
|
||||||
{
|
{
|
||||||
public class OAuthTokenResponse
|
public class OAuthTokenResponse
|
||||||
{
|
{
|
||||||
public OAuthTokenResponse(JObject response)
|
private OAuthTokenResponse(JObject response)
|
||||||
{
|
{
|
||||||
Response = response;
|
Response = response;
|
||||||
AccessToken = response.Value<string>("access_token");
|
AccessToken = response.Value<string>("access_token");
|
||||||
|
|
@ -16,10 +17,26 @@ namespace Microsoft.AspNet.Authentication.OAuth
|
||||||
ExpiresIn = response.Value<string>("expires_in");
|
ExpiresIn = response.Value<string>("expires_in");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private OAuthTokenResponse(Exception error)
|
||||||
|
{
|
||||||
|
Error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OAuthTokenResponse Success(JObject response)
|
||||||
|
{
|
||||||
|
return new OAuthTokenResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OAuthTokenResponse Failed(Exception error)
|
||||||
|
{
|
||||||
|
return new OAuthTokenResponse(error);
|
||||||
|
}
|
||||||
|
|
||||||
public JObject Response { get; set; }
|
public JObject Response { get; set; }
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; }
|
||||||
public string TokenType { get; set; }
|
public string TokenType { get; set; }
|
||||||
public string RefreshToken { get; set; }
|
public string RefreshToken { get; set; }
|
||||||
public string ExpiresIn { get; set; }
|
public string ExpiresIn { get; set; }
|
||||||
|
public Exception Error { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,8 +36,7 @@ namespace Microsoft.AspNet.Authentication
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.Response.StatusCode = 500;
|
throw new AggregateException("Unhandled remote error.", errorContext.Error);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have a ticket if we get here
|
// We have a ticket if we get here
|
||||||
|
|
|
||||||
|
|
@ -244,8 +244,8 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
options.ClientId = "Test Id";
|
options.ClientId = "Test Id";
|
||||||
options.ClientSecret = "Test Secret";
|
options.ClientSecret = "Test Secret";
|
||||||
});
|
});
|
||||||
var transaction = await server.SendAsync("https://example.com/signin-google?code=TestCode");
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => server.SendAsync("https://example.com/signin-google?code=TestCode"));
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
Assert.Equal("The oauth state was missing or invalid.", error.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -270,15 +270,16 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var transaction = await server.SendAsync("https://example.com/signin-google?error=OMG");
|
|
||||||
if (redirect)
|
if (redirect)
|
||||||
{
|
{
|
||||||
|
var transaction = await server.SendAsync("https://example.com/signin-google?error=OMG");
|
||||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
Assert.Equal("/error?ErrorMessage=OMG", transaction.Response.Headers.GetValues("Location").First());
|
Assert.Equal("/error?ErrorMessage=OMG", transaction.Response.Headers.GetValues("Location").First());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => server.SendAsync("https://example.com/signin-google?error=OMG"));
|
||||||
|
Assert.Equal("OMG", error.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,7 +380,8 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
{
|
{
|
||||||
Sender = req =>
|
Sender = req =>
|
||||||
{
|
{
|
||||||
return new HttpResponseMessage(HttpStatusCode.BadRequest);
|
return ReturnJsonResponse(new { Error = "Error" },
|
||||||
|
HttpStatusCode.BadRequest);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (redirect)
|
if (redirect)
|
||||||
|
|
@ -402,24 +404,21 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
properties.RedirectUri = "/me";
|
properties.RedirectUri = "/me";
|
||||||
|
|
||||||
var state = stateFormat.Protect(properties);
|
var state = stateFormat.Protect(properties);
|
||||||
|
var sendTask = server.SendAsync(
|
||||||
await Assert.ThrowsAsync<HttpRequestException>(() => server.SendAsync(
|
|
||||||
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.Encode(state),
|
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.Encode(state),
|
||||||
correlationKey + "=" + correlationValue));
|
correlationKey + "=" + correlationValue);
|
||||||
|
if (redirect)
|
||||||
//var transaction = await server.SendAsync(
|
{
|
||||||
// "https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.Encode(state),
|
var transaction = await sendTask;
|
||||||
// correlationKey + "=" + correlationValue);
|
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
//if (redirect)
|
Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.Encode("OAuth token endpoint failure: Status: BadRequest;Headers: ;Body: {\"Error\":\"Error\"};"),
|
||||||
//{
|
transaction.Response.Headers.GetValues("Location").First());
|
||||||
// Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
}
|
||||||
// Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.Encode("Access token was not found."),
|
else
|
||||||
// transaction.Response.Headers.GetValues("Location").First());
|
{
|
||||||
//}
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => sendTask);
|
||||||
//else
|
Assert.Equal("OAuth token endpoint failure: Status: BadRequest;Headers: ;Body: {\"Error\":\"Error\"};", error.Message);
|
||||||
//{
|
}
|
||||||
// Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -459,18 +458,20 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
properties.Items.Add(correlationKey, correlationValue);
|
properties.Items.Add(correlationKey, correlationValue);
|
||||||
properties.RedirectUri = "/me";
|
properties.RedirectUri = "/me";
|
||||||
var state = stateFormat.Protect(properties);
|
var state = stateFormat.Protect(properties);
|
||||||
var transaction = await server.SendAsync(
|
var sendTask = server.SendAsync(
|
||||||
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.Encode(state),
|
"https://example.com/signin-google?code=TestCode&state=" + UrlEncoder.Default.Encode(state),
|
||||||
correlationKey + "=" + correlationValue);
|
correlationKey + "=" + correlationValue);
|
||||||
if (redirect)
|
if (redirect)
|
||||||
{
|
{
|
||||||
|
var transaction = await sendTask;
|
||||||
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.Encode("Access token was not found."),
|
Assert.Equal("/error?ErrorMessage=" + UrlEncoder.Default.Encode("Failed to retrieve access token."),
|
||||||
transaction.Response.Headers.GetValues("Location").First());
|
transaction.Response.Headers.GetValues("Location").First());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => sendTask);
|
||||||
|
Assert.Equal("Failed to retrieve access token.", error.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -712,9 +713,8 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task NoStateCauses500()
|
public async Task NoStateCausesException()
|
||||||
{
|
{
|
||||||
var stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
|
|
||||||
var server = CreateServer(options =>
|
var server = CreateServer(options =>
|
||||||
{
|
{
|
||||||
options.ClientId = "Test Id";
|
options.ClientId = "Test Id";
|
||||||
|
|
@ -722,10 +722,8 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
});
|
});
|
||||||
|
|
||||||
//Post a message to the Google middleware
|
//Post a message to the Google middleware
|
||||||
var transaction = await server.SendAsync(
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => server.SendAsync("https://example.com/signin-google?code=TestCode"));
|
||||||
"https://example.com/signin-google?code=TestCode");
|
Assert.Equal("The oauth state was missing or invalid.", error.Message);
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -756,9 +754,9 @@ namespace Microsoft.AspNet.Authentication.Google
|
||||||
transaction.Response.Headers.GetValues("Location").First());
|
transaction.Response.Headers.GetValues("Location").First());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpResponseMessage ReturnJsonResponse(object content)
|
private static HttpResponseMessage ReturnJsonResponse(object content, HttpStatusCode code = HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
var res = new HttpResponseMessage(HttpStatusCode.OK);
|
var res = new HttpResponseMessage(code);
|
||||||
var text = JsonConvert.SerializeObject(content);
|
var text = JsonConvert.SerializeObject(content);
|
||||||
res.Content = new StringContent(text, Encoding.UTF8, "application/json");
|
res.Content = new StringContent(text, Encoding.UTF8, "application/json");
|
||||||
return res;
|
return res;
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task BadSignInWill500()
|
public async Task BadSignInWillThrow()
|
||||||
{
|
{
|
||||||
var server = CreateServer(options =>
|
var server = CreateServer(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -73,10 +73,8 @@ namespace Microsoft.AspNet.Authentication.Twitter
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send a bogus sign in
|
// Send a bogus sign in
|
||||||
var transaction = await server.SendAsync(
|
var error = await Assert.ThrowsAnyAsync<Exception>(() => server.SendAsync("https://example.com/signin-twitter"));
|
||||||
"https://example.com/signin-twitter");
|
Assert.Equal("Invalid state cookie.", error.Message);
|
||||||
|
|
||||||
Assert.Equal(HttpStatusCode.InternalServerError, transaction.Response.StatusCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue