I'm using the Google APIs for .NET. I'm following the example project Simple OAuth2 but I keep getting a Protocol Exception from DotNetOpenAuth.
Here's what I have now:
public static void Main( string[] args )
{
// Register the authenticator.
NativeApplicationClient provider = new NativeApplicationClient(
GoogleAuthenticationServer.Description, gaAppId, gaSecret );
OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(
provider, GetAuthorization );
AnalyticsService analyticsService =
new AnalyticsService( new BaseClientService.Initializer {
Authenticator = auth,
ApplicationName = #"Test Application",
} );
DataResource.GaResource.GetRequest request = analyticsService.Data.Ga.Get(
gaId, #"2013-09-04", #"2013-09-18", "ga:totalEvents" );
GaData data = request.Execute();
Console.ReadKey();
}
private static IAuthorizationState GetAuthorization( NativeApplicationClient arg )
{
// Get the auth URL:
IAuthorizationState state =
new AuthorizationState( new[] {AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue()} );
state.Callback = new Uri( NativeApplicationClient.OutOfBandCallbackUrl );
// Retrieve the access token by using the authorization code:
return arg.ProcessUserAuthorization( authCode, state );
}
Notes:
I'm using the Analytics API in this code because that's what I need. I get the same error when using the Tasks API as described in the sample.
The authentication code is a refresher token generated by the process as defined in the example code. The error comes in both cases (request a new token, or re-use an old one.)
The ProtocolException that is triggered by DotNetOpenAuth is there because accounts.google.com returns an error: invalid request.
Here's what the OAuth request looks like:
Aplication/x-www-form-urlencoded; charset=utf-8
User-Agent: DotNetOpenAuth/4.3.1.13153
Host: accounts.google.com
Cache-Control: no-store,no-cache
Pragma: no-cache
Content-Length: 148
Connection: Keep-Alive
code=REDACTED&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&grant_type=authorization_code
And this is what the Google returns:
HTTP/1.1 400 Bad Request
IRRELEVANT_HEADERS
{
"error" : "invalid_request"
}
I'm at a bit of a loss here as to why this happend and how I can solve this. I can't find any other working C# examples. It seems to be a different kind of error than the one in this thread. Is there any of you who knows how I can fix this?
I studied the examples provided by peleyal some more and managed to find the issue. The problem was that I wasn't actually storing a refresh-token, I was storing an authentication token. I should've used the authentication token to generate the refresh token.
Here's the full solution in a simplified manner for future reference. The differences can be found in the GetAuthorization function, which now correctly saves the refresh token and opens a browser window to request the authorization when a refresh token is not yet available.
private static refreshToken = null;
public static void Main( string[] args )
{
// Register the authenticator.
NativeApplicationClient provider = new NativeApplicationClient(
GoogleAuthenticationServer.Description, gaAppId, gaSecret );
OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(
provider, GetAuthorization );
AnalyticsService analyticsService = new AnalyticsService( new BaseClientService.Initializer {
Authenticator = auth,
ApplicationName = #"Test Application",
} );
DataResource.GaResource.GetRequest request = analyticsService.Data.Ga.Get( gaId, #"2013-09-04",
#"2013-09-18", #"ga:totalEvents" );
GaData data = request.Execute();
Console.ReadKey();
}
private static IAuthorizationState GetAuthorization( NativeApplicationClient arg )
{
// Get the auth URL:
IAuthorizationState state = new AuthorizationState(
new[] {AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue()} );
state.Callback = new Uri( NativeApplicationClient.OutOfBandCallbackUrl );
if( !string.IsNullOrEmpty( refreshToken ) ) {
try {
state.RefreshToken = refreshToken;
arg.RefreshToken( state );
} catch {
refreshToken = null;
}
}
// If the refresh token is empty, request a new one by opening
// a browser window. Allows the user to paste its authorization token
// and saves the refresh token.
if( string.IsNullOrEmpty( refreshToken ) ) {
Uri authUri = arg.RequestUserAuthorization( state );
// Request authorization from the user (by opening a browser window):
Process.Start( authUri.ToString() );
Console.Write( " Authorization Code: " );
string authCode = Console.ReadLine();
Console.WriteLine();
// Retrieve the access token by using the authorization code:
state = arg.ProcessUserAuthorization( authCode, state );
refreshToken = state.RefreshToken;
}
return state;
}
Related
I have a Web API service hosted in Microsoft Azure. I need a certain POST method to be only accessible with one unique username and password.
I understand the [Authorize] method does a token based authentication but its not tied to a single username and password. In my app, the web api also does the login authentication, so anyone who registers can access this post method if im not mistaken. (Please correct me if im wrong)
I am new to this could you guide me the right way please.
This is my WebAPI Post method i want to secure access to with specific unique username&pass:
[AllowAnonymous]
[HttpPost, Route("send")]
public async Task<NotificationOutcome> Post([FromBody]string message)
{
string hubName = "myHub";
string hubNameDefaultShared = "myHubNameDefaultShared";
NotificationHubClient hub = NotificationHubClient
.CreateClientFromConnectionString(hubNameDefaultShared, hubName, enableTestSend: true);
string installationId = string.Empty;
var templateParams = new Dictionary<string, string>
{
["messageParam"] = message
};
NotificationOutcome result = null;
if (string.IsNullOrWhiteSpace(installationId))
{
result = await hub.SendTemplateNotificationAsync(templateParams).ConfigureAwait(false);
}
else
{
result = await hub.SendTemplateNotificationAsync(templateParams, "$InstallationId:{" + installationId + "}").ConfigureAwait(false);
}
return result;
}
And this is how I currently access the POST Method:
var client = new RestClient("myWebApiRouteName");
var request = new RestRequest(Method.POST);
request.AddHeader("Postman-Token", "46c23eba-8ca6-4ede-b4fe-161473dc063a");
request.AddHeader("cache-control", "no-cache");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("undefined", messageBody, ParameterType.RequestBody);
try
{
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
My web application is a kind of wrapper for some 3rd party service. This 3rd party service uses the JWT Bearer authentication to access its WebAPI endpoints. The tokens are encrypted with RS256 algorithm (asymmetric).
I have a Public Key to validate tokens signature on my side. It is easy to validate signature on jwt.io site (just paste the token and public key to the text boxes). But how do I configure TokenValidationParameters to have tokens validated automatically using specified Public Key?
AddAuthentication code snippet:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.ValidIssuer = "iss";
options.TokenValidationParameters.ValidateIssuerSigningKey = true;
options.TokenValidationParameters.IssuerSigningKey = SomeCodeToGenerateSecurityKeyUsingPublicKeyOnly("-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----");
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
});
services.AddAuthorization(options =>
{
options.AddPolicy("Bearer",
new AuthorizationPolicyBuilder(new string[] { JwtBearerDefaults.AuthenticationScheme })
.RequireAuthenticatedUser()
.Build()
);
});
I can't just use SymmetricSecurityKey class like this:
options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("..."));
because of asymmetric encryption. In this case an exception occurs:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey , KeyId:
'.
Exceptions caught:
''.
token: '{"alg":"RS256","typ":"JWT"}....
Okay, eventually I was succeeded with following solution. The RsaSecurityKey object do the trick I was looking for:
byte[] publicKeyBytes = Convert.FromBase64String("public_key_without_header_and_footer");
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
RSAParameters rsaParameters = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned(),
};
......
options.TokenValidationParameters.IssuerSigningKey = new RsaSecurityKey(rsaParameters);
I'm not sure if this is the best solution, but I haven't some another one for now.
I am using pretty much this approach to secure our api with tokens. Not sure, how to handle token expiry response on client side. I have seen two options on api side -
Challenge
WWW-Authenticate header
Let's say my client is requesting for contacts list but their token is expired as it was issued 30 mins ago (our current token age). What do we do now?
Not a good experience if we ask for credentials again
use a do-while loop until we have a successful response. Our client is a plugin so we can store user's credentials in db.
try\catch could be a possibility too
Implementing IdentityServer and refresh tokens would be a overkill for us at this point.
There are several ways you can deal this getting new tokens after they expire. Its not a good experience asking for credentials every time the token expires. Some of the ways to achieve this is to use MemoryCache or text file to store your token then keep reading from it. Once the token expires get a new one and replace the one you stored earlier. Sample code below
public async Task<string> GetToken(HttpClient client)
{
Token token;
MemoryCache memCache = MemoryCache.Default;
var resource = memCache.Get(Settings.TokenKey);
if (resource.IsNotNull())
{
token = (Token)resource;
if (token != null && token.Expires < DateTime.Now)
{
token = await RequestNewToken(client);
return token.AccessToken;
}
}
token = await RequestNewToken(client);
return token.AccessToken;
}
private async Task<Token> RequestNewToken(HttpClient client)
{
var tokenRequestContent = GetTokenRequestContent();
var tokenResponse = client.PostAsync(Settings.TokenUrl, tokenRequestContent).Result;
Token token = null;
MemoryCache memCache = MemoryCache.Default;
if (tokenResponse.IsSuccessStatusCode)
{
token = await tokenResponse.Content.ReadAsAsync<Token>();
memCache.Add(Settings.TokenKey, token, DateTimeOffset.UtcNow.AddHours(2));
}
else
{
var error = await tokenResponse.Content.ReadAsStringAsync();
Console.WriteLine(error);
}
if (token == null) throw new NullReferenceException("Token is null");
return token;
}
private FormUrlEncodedContent GetTokenRequestContent()
{
var credentials = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", Settings.Password),
new KeyValuePair<string, string>("userName", Settings.Username),
new KeyValuePair<string, string>("password", Settings.UserPassword)
};
return new FormUrlEncodedContent(credentials);
}
Settings is just a static class that holds your constants.
I have a web app MVC,using auth0 owin regular web app cookie based authentication.
This web app also has webapis which is used internally in the application. However i have a requirement to call this webapis from outside the application. So i created a restclient and tried to implement jwtbearerauthentication in application (but cookie based on authentication still in place).
Now when i call the webapi from other application it validates the bearer token gives no error however it redirects to login page due to cookie based authentication.
startup file:
public partial class Startup
{
private IPlatform platform;
public void ConfigureAuth(IAppBuilder app, IPlatform p, IContainer container)
{
platform = p;
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromDays(2),
SlidingExpiration = true
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
var provider = new Auth0.Owin.Auth0AuthenticationProvider
{
OnReturnEndpoint = (context) =>
{
// xsrf validation
if (context.Request.Query["state"] != null && context.Request.Query["state"].Contains("xsrf="))
{
var state = HttpUtility.ParseQueryString(context.Request.Query["state"]);
AntiForgery.Validate(context.Request.Cookies["__RequestVerificationToken"], state["xsrf"]);
}
return System.Threading.Tasks.Task.FromResult(0);
},
OnAuthenticated = (context) =>
{
var identity = context.Identity;
//Add claims
var authenticationManager = container.Resolve<IAuthenticationManager>();
authenticationManager.AddClaims(identity);
if (context.Request.Query["state"] != null)
{
authenticationManager.AddReturnUrlInClaims(identity, context.Request.Query["state"]);
}
return System.Threading.Tasks.Task.FromResult(0);
}
};
var issuer = "https://" + ConfigurationManager.AppSettings["auth0:Domain"] + "/";
var audience = ConfigurationManager.AppSettings["auth0:ClientId"];
var secret = TextEncodings.Base64.Encode(TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["auth0:ClientSecret"]));
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseAuth0Authentication(
clientId: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientId"),
clientSecret: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientSecret"),
domain: platform.ServerRole.GetConfigurationSettingValue("auth0:Domain"),
provider: provider);
}
}
webapiconfig file:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Filters.Add(new AuthorizeAttribute());
ODataConfig.Setup(config);
var clientID = WebConfigurationManager.AppSettings["auth0:ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["auth0:ClientSecret"];
config.MessageHandlers.Add(new JsonWebTokenValidationHandler()
{
Audience = clientID,
SymmetricKey = clientSecret
});
}
Currently creating the jwt token from below code and posting using postman in header just to check if it works.. but redirects to login page.
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
I suspect what's happening is that your call to the API has a bearer token which fails validation (or there is no Authorize token at all), your API controller has an Authorize attribute, which, since there is no valid ClaimsPrincipal on the call throws 401. Auth0AuthenticationProvider picks that and assumes the call was to UI so redirects for user authentication. You may want to add an override in the Oauth0Provider to trap OnRedirectToIdP (or something like that), inspect the request and if it is to API, abot further handling and return Unauthorized.
Remove any [Authorize] from your API and see whether it works then. Also make sure your startup does not require Authorize for all controllers.
You may want to remove the authn part of your code (cookie and Oauth2Provider) and see whether you are getting to the API then.
A few years late i know, but i recently came across the same requirement in a project, and found this sample put together by a dev at Auth0.
https://github.com/auth0-samples/aspnet-core-mvc-plus-webapi
The example in the link allows for cookie authentication OR token authentication for the API endpoints.
The key takeaway for me was using attributes on your routes to tell the pipline what authentication mechanism to use. In my case i wanted cookie authentication for the UI and token authentication for the endpoints. i had no requirement to use both for any single area of the project.
controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
[Route("api")]
public string TestAuth()
{
return "All good " + this.User.FindFirst(ClaimTypes.NameIdentifier).Value + ". You only get this message if you are authenticated.";
}
I have a problem with using the RestSharp client on the Windows Phone device. Starting form the beginning, I have the ASP.NET Web Api service hosted online. I have a request user address: POST: http://my-service-url.com/token where I send Email and Password as a body parameters and I get 201 status code and a cookie in the response. When I do it in fiddler everything works fine. I also have the functional test for my API which is using the RestSharp which is also working correctly:
[Given(#"I fill email and password with correct data and I click log in button")]
public void GivenIFillEmailAndPasswordWithCorrectDataAndIClickLogInButton()
{
//Delete user if he exists
_testUserHelper.DeleteTestUser(TestEmail);
//Create new activated user
Assert.IsTrue(_testUserHelper.CreateTestUser(TestEmail, TestPassword));
//Prepare client
var client = new RestClient(CarRentalsConstants.HostAddress);
var restRequest = new RestRequest("api/token", Method.POST) { RequestFormat = DataFormat.Json };
//Add parameters to request
restRequest.AddBody(new { Email = TestEmail, Password = TestPassword });
//Perform request
_response = client.Execute(restRequest);
}
[When(#"the log in login process finishes")]
public void WhenTheLogInLoginProcessFinishes()
{
Assert.AreEqual(HttpStatusCode.Created, _response.StatusCode, _response.Content);
Assert.IsNotNull(_response.Cookies.SingleOrDefault(q => q.Name == ".ASPXAUTH"););
}
The one above works properly, and the cookie is in the response object.
Now what I try to do on my windows phone looks like this:
var restRequest = new RestRequest("token", Method.POST) {RequestFormat = DataFormat.Json};
restRequest.AddBody(new {Email = email, Password = password});
myWebClient.ExecuteAsync(restRequest, (restResponse, handle) =>
{
switch (restResponse.StatusCode)
{
case HttpStatusCode.Created:
{
var cookie = restResponse.Cookies.SingleOrDefault(q => q.Name == ".ASPXAUTH");
successLogicDelegate(cookie);
}
break;
case HttpStatusCode.BadRequest:
{
HandleBadRequest(restResponse.Content, failureLogicDelegate);
}
break;
default:
msgBox.Show(StringResources.ServerConnectionError);
failureLogicDelegate(null);
break;
}
});
And in this case, the response returns the "Created" status code, but the cookie is then set to null. I have no idea what is happening here, but I am fairly certain that server is sending this cookie, so where does it get lost?
Any help will be really appreciated.