I'm new in api and very much confused,I have implemented a basic authentication,it authenticate the user and I get 200 status, but then why it does not reach my controller?
my web api config:
config.Filters.Add(new BasicAuthenticationAttribute());
// config.Filters.Add(new AuthorizeAttribute());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
my controller:
[BasicAuthentication]
public HttpResponseMessage Get(string id, string dttimeFrom, string dttimeTo)
{
var _context = new TenMinutesContext();
var rslt = (from m in _context.users
// where m.m_turbine_id == id
select new User
{
m_user_name = m.m_user_name
// m.m_voltage_u12=
}).Take(10).ToList();
return Request.CreateResponse(HttpStatusCode.OK,
rslt);
in postman I put http://localhost:51154/api/values?id=192.168.39.82&dttimeFrom=05102019&dttimeTo=10102019
and I get the user is authenticated,but why I cant see the controller result?
my BasicAuthentication Class:
public class BasicAuthenticationAttribute: AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
// Replace this with your own system of security / means of validating credentials
var isValid = userName == "rene" && password == "2019";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
Thread.CurrentPrincipal = principal;
actionContext.Response =
actionContext.Request.CreateResponse(HttpStatusCode.OK,
"User " + userName + " successfully authenticated");
base.OnAuthorization(actionContext);
}
else
{
HandleUnathorized(actionContext);
}
}
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
}
}
Related
Following this guide (https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth#web-applications-asp.net-mvc) I made the code work in localhost but it gives an Error 400: invalid_request device_id and device_name are required for private IP: http://xx.xx.xx.xx:xxxxx/AuthCallback/IndexAsync while redirecting to Authorization page. We added subdomain in dns for our windows server ip address and registered the same subdomain in google api console. Why google gets ip address of the server instead of subdomain? Is the problem related to dns?
These are the working codes on local machine:
HomeController.cs
public class HomeController : Controller
{
public async Task<ActionResult> Index(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new GmailService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "BPM Mail"
});
var gmailProfile = service.Users.GetProfile("me").Execute();
var userGmailEmail = gmailProfile.EmailAddress;
Utils.userMail = userGmailEmail;
Utils.cred = result.Credential;
// SAMPLE CODE:
//var list = await service.Files.List().ExecuteAsync();
//ViewBag.Message = "FILE COUNT IS: " + list.Items.Count();
return new RedirectResult("~/Apps/Mail/Default.aspx");
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
}
AppFlowMetadata.cs
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "ClientId ",
ClientSecret = "ClientSecret "
},
Scopes = new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailModify },
DataStore = new FileDataStore("D:/bpm_mail/mytokens/token.json", true)
});
public override string GetUserId(Controller controller)
{
// In this sample we use the session to store the user identifiers.
// That's not the best practice, because you should have a logic to identify
// a user. You might want to use "OpenID Connect".
// You can read more about the protocol in the following link:
// https://developers.google.com/accounts/docs/OAuth2Login.
var user = controller.Session["user"];
//Utils.userId = user.ToString();
if (user == null)
{
user = Guid.NewGuid();
controller.Session["user"] = user;
}
return user.ToString();
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
//public override string AuthCallback
//{
// get { return #"/AuthCallback/IndexAsync"; }
//}
}
AuthCallbackController.cs
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
Issue solved by changing private ip to public one and redirect it to domain.
I am working on the Unit Testing in Asp.Net Mvc Web Api.
I have 2 projects
1: Catalog.Api - This contains all the controllers
2: Catalog.UnitTests - This contains the Unit Test for controllers
All Controllers are Inherit with "ApiController" and every controller has custom filter [AuthenticationFilter]. Here is my values controller.
[AuthenticationFilter]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
And my custom is check the authorization token. Here it is
public class AuthenticationFilter: AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var request = actionContext.Request;
var authorization = request.Headers.Authorization;
if (authorization == null || authorization.Scheme != "Bearer")
{
ShowAuthenticationError(actionContext, "Authorization required");
return;
}
if (string.IsNullOrEmpty(authorization.Parameter))
{
ShowAuthenticationError(actionContext, "Missing Jwt Token");
return;
}
var token = authorization.Parameter;
var principal = AuthenticateToken(token);
if (principal == null)
{
ShowAuthenticationError(actionContext, "Invalid token");
return;
}
base.OnAuthorization(actionContext);
}
private static void ShowAuthenticationError(HttpActionContext filterContext, string message)
{
var responseDTO = new ResponseDTO() { Code = 401, Message = message };
filterContext.Response =
filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized, responseDTO);
}
}
public class ResponseDTO
{
public int Code { get; set; }
public string Message { get; set; }
}
Now in the Unit Test project i have a class and unit test method.
[TestMethod]
public void CheckFilter()
{
try
{
var controller = new ValuesController();
var controllerContext = new HttpControllerContext();
var request = new HttpRequestMessage();
request.Headers.Add("Authorization", "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InVhbGkiLCJlbWFpbCI6InVhbGlAaW5yZWFjaGNlLmNvbSIsIm5iZiI6MTU2NDY0NjIyMSwiZXhwI");
controllerContext.Request = request;
controller.ControllerContext = controllerContext;
var result = controller.Get();
Assert.IsTrue(result.Any());
}
catch (Exception ex)
{
Assert.Fail();
}
}
I am calling my controller by adding reference of API project into my unit test project. So all controllers are available in the unit test project.
Issue is that when i call the values controller it always return the data. And when i remove the request and header so it is also returning the data but in that case that will be unauthorized.
I think my custom filter is not calling. How should that would be called and authenticate the user.
I check your question and configure that issue it is basically you are calling the controller directly.
Basically controller is a class and when you are calling that it is behaving like a simple class and call the method and send back the result. It is simple and clear
But in your situation you have project for your api so can do this.
[TestMethod]
public void CheckFilter()
{
try
{
var config = new HttpConfiguration();
// This is the resgister method which is written in you Api project. That code is after this method this method because i did the same thing to call my controller.
Catalog.Api.WebApiConfig.Register(config);
using (var server = new HttpServer(config))
{
var client = new HttpClient(server);
string url = "http://localhost:PortNumberOfProject/api/values";
var request = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = HttpMethod.Get
};
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "Your Token");
var response = await client.SendAsync(request);
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
}
catch (Exception ex)
{
Assert.Fail();
}
}
Here is the WebApi Register method of Api project which is used to register the Api and Routes.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Here is your controller as it is. And now debug your test and add a break point in your [AuthenticationFilter] and OnAuthorization method.
[AuthenticationFilter]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
We are using packages Google.Apis.Calendar.v3 and Google.Apis.Auth.Mvc in an ASP .NET MVC application with users.
We want each user to have access to their Google Calendar, so it is necessary that each one of them give permission to our application. We have read a lot and it is said that there is a way to prevent the application from asking the user for these credentials every hour
We have looked at solutions like this and do not work for us
solutions like this one we do not know how to use them in our MVC application
users may or may not register on our site using google
This is the last code that we tried and everything works correctly, except that every hour the users who had already given permission to our application had to do it again and to re-authenticate with google and give permissions:
FlowMetadata implementation
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new ForceOfflineGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "MY_VALID_CLIENT_ID",
ClientSecret = "MY_VALID_CLIENT_SECRET"
},
Scopes = new[] { CalendarService.Scope.Calendar },
DataStore = new FileDataStore("Google.Api.Auth.Store")
});
public override string GetUserId(Controller controller)
{
var userId = controller.User.Identity.GetUserId();
return userId;
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
GoogleAuthorizationCodeFlow implementation forcing offline
internal class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public ForceOfflineGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
{
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
AccessType = "offline",
ApprovalPrompt = "force"
};
}
}
AuthCallbackController
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
Test controller action
public async Task IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = result.Credential,
ApplicationName = "TEST",
});
IList list = service.CalendarList.List().Execute().Items;
var selected = list.First();
EventsResource.ListRequest request = service.Events.List(selected.Id);
request.TimeMin = new DateTime?(new DateTime(2017, 1, 1));
request.ShowDeleted = false;
request.SingleEvents = true;
request.MaxResults = 10;
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
Events events = request.Execute();
return View(events);
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
I am using Thinktecture AuthenticationConfiguration to provide an end point for signing tokens on my API:
var authConfig = new AuthenticationConfiguration
{
EnableSessionToken = true,
SendWwwAuthenticateResponseHeaders = true,
RequireSsl = false,
ClaimsAuthenticationManager = new ClaimsTransformation(),
SessionToken = new SessionTokenConfiguration
{
EndpointAddress = "/api/token",
SigningKey = signingKey,
DefaultTokenLifetime = new TimeSpan(1, 0, 0)
}
};
var userCredentialsService = new CredentialsService(credentialStore);
authConfig.AddBasicAuthentication(userCredentialsService.Validate);
And authenticating users with CredentialsService:
public class CredentialsService
{
public bool Validate(string username, string password)
{
return username == password;
}
}
The above works, and no its certainly not used in production, but on returning true i will get a token in which contains a claim with the username.
In my scenario I have a user id (an integer) which can never change and I would like this to be in my claim. So the user would pass an email address to the service endpoint in the header as basic authentication, and then if valid go ahead and sign with the id as the claim (but not the email address as the claim):
public class CredentialsService
{
public bool Validate(string emailAddress, string password)
{
// map from the provided name, to the user id
var details = MySqlDb.ReadBy(emailAddress);
var id = details.Id; // this is the actual identity of the user
var email = details.EmailAddress;
var hash = details.Hash;
return PasswordHash.ValidatePassword(password,hash);
}
}
I appreciate this will need a second lookup to a sql server database to transform the emailAddress in to a userId, is there a way for me to insert this in to the pipeline flow before CredentialsService is called?
Or am i going about it the wrong way, and just stick with the username that was signed in as, then use a claims transformation based on the username to enrich with the integer identity - but then what if they changed the username?
Ok, I managed to solve this by taking a look at the awesome thinktecture source and overriding BasicAuthenticationSecurityTokenHandler to give a derived class which has a second delegate returning a Claim[] ready to be signed:
public class BasicAuthSecurityTokenHandlerWithClaimsOutput : BasicAuthenticationSecurityTokenHandler
{
public BasicAuthSecurityTokenHandlerWithClaimsOutput(ValidateUserNameCredentialDelegate validateUserNameCredential, GetClaimsForAuthenticatedUser getClaimsForAuthenticatedUser)
: base()
{
if (validateUserNameCredential == null)
{
throw new ArgumentNullException("ValidateUserNameCredential");
}
if (getClaimsForAuthenticatedUser== null)
{
throw new ArgumentNullException("GetClaimsForAuthenticatedUser");
}
base.ValidateUserNameCredential = validateUserNameCredential;
_getClaimsForAuthenticatedUser = getClaimsForAuthenticatedUser;
}
public delegate Claim[] GetClaimsForAuthenticatedUser(string username);
private readonly GetClaimsForAuthenticatedUser _getClaimsForAuthenticatedUser;
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
if (token == null)
{
throw new ArgumentNullException("token");
}
if (base.Configuration == null)
{
throw new InvalidOperationException("No Configuration set");
}
UserNameSecurityToken unToken = token as UserNameSecurityToken;
if (unToken == null)
{
throw new ArgumentException("SecurityToken is not a UserNameSecurityToken");
}
if (!ValidateUserNameCredentialCore(unToken.UserName, unToken.Password))
{
throw new SecurityTokenValidationException(unToken.UserName);
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, unToken.UserName),
new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password),
AuthenticationInstantClaim.Now
};
var lookedUpClaims = _getClaimsForAuthenticatedUser(unToken.UserName);
claims.AddRange(lookedUpClaims);
if (RetainPassword)
{
claims.Add(new Claim("password", unToken.Password));
}
var identity = new ClaimsIdentity(claims, "Basic");
if (Configuration.SaveBootstrapContext)
{
if (this.RetainPassword)
{
identity.BootstrapContext = new BootstrapContext(unToken, this);
}
else
{
var bootstrapToken = new UserNameSecurityToken(unToken.UserName, null);
identity.BootstrapContext = new BootstrapContext(bootstrapToken, this);
}
}
return new List<ClaimsIdentity> {identity}.AsReadOnly();
}
}
I then added a second helper method to make it easier to wire up:
public static class BasicAuthHandlerExtensionWithClaimsOutput
{
public static void AddBasicAuthenticationWithClaimsOutput(
this AuthenticationConfiguration configuration,
BasicAuthenticationSecurityTokenHandler.ValidateUserNameCredentialDelegate validationDelegate,
BasicAuthSecurityTokenHandlerWithClaimsOutput.GetClaimsForAuthenticatedUser getClaimsForAuthenticatedUserDelegate,
string realm = "localhost", bool retainPassword = false)
{
var handler = new BasicAuthSecurityTokenHandlerWithClaimsOutput(validationDelegate, getClaimsForAuthenticatedUserDelegate);
handler.RetainPassword = retainPassword;
configuration.AddMapping(new AuthenticationOptionMapping
{
TokenHandler = new SecurityTokenHandlerCollection { handler },
Options = AuthenticationOptions.ForAuthorizationHeader(scheme: "Basic"),
Scheme = AuthenticationScheme.SchemeAndRealm("Basic", realm)
});
}
}
Hope this helps others, please let me know if i have done something horrific!
I'm experiencing a situation that I have find nowhere in other articles. I'm designing a RESTful server to be consumed by an mobile app. In this case, username and password are part of header in app call, there is no logon screen.
The following code does the job of validating user info and the controllers has security controlled.
My question is: how can I populate iPrincipal in the ApiController controllers?
I have created a filter addressed at WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Filtro de login
config.Filters.Add(new tbAuthorize());
The code for tbAuthorize is:
public class tbAuthorize : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
string username;
string password;
if (GetUserNameAndPassword(actionContext, out username, out password))
{
if (!isUserAuthorized(username, password))
return false;
else
{
//Users = username;
return true;
}
}
else
return false;
}
private bool GetUserNameAndPassword(HttpActionContext actionContext, out string username, out string password)
{
username = "";
password = "";
if (actionContext.Request.Headers.Authorization == null) return false;
// Convert 64 code to separated string[]
string[] s = ParseAuthHeader(actionContext.Request.Headers.Authorization.ToString());
if (s == null)
return false;
username = s[0];
password = s[1];
return true;
}
private string[] ParseAuthHeader(string authHeader)
{
// Check this is a Basic Auth header
if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
// Pull out the Credentials with are seperated by ':' and Base64 encoded
string base64Credentials = authHeader.Substring(6);
string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;
// Okay this is the credentials
return credentials;
}
private bool isUserAuthorized(string username, string password)
{
// Valid the user at database
var userId = new UsersController().Login(username, password);
// Membership.GetUser() is null
//Users = Membership.GetUser().Email;
return userId != 0;
}
}
The issue is that I have no access to a cookie in Response and I did not find to way to populate iPrincipal.
I need to has data in this.User.Identity.Name, as this:
[tbAuthorize]
public class UsersController : ApiController
{
public void test()
{
string x = this.User.Identity.Name;
}
Thanks for any help,
Marco Castro
Authentication and Authorization are two differents things. Before authorizing a user you have to authenticate their.
With WebApi you have the concept of pipeline with Delegatinghandler. Message goes from one handler to the next until one send the response. I recommend you to create a DelegatingHandler to authentificate users. Then you can use AuthorizeAttribute to prevent unauthenticated user to access your API.
Here's an example to authenticate user with HTTP Basic
public abstract class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic Realm=\"{0}\"";
protected BasicAuthMessageHandler()
{
}
protected BasicAuthMessageHandler(HttpConfiguration httpConfiguration)
{
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected virtual string GetRealm(HttpRequestMessage message)
{
return message.RequestUri.Host;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Process request
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter) &&
string.Equals(authValue.Scheme, "basic", StringComparison.OrdinalIgnoreCase))
{
// Try to authenticate user
IPrincipal principal = ValidateHeader(authValue.Parameter);
if (principal != null)
{
request.GetRequestContext().Principal = principal;
}
}
return base.SendAsync(request, cancellationToken) // Send message to the InnerHandler
.ContinueWith(task =>
{
// Process response
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized &&
!response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader,
string.Format(BasicAuthResponseHeaderValue, GetRealm(request)));
}
return response;
}, cancellationToken);
}
private IPrincipal ValidateHeader(string authHeader)
{
// Decode the authentication header & split it
var fromBase64String = Convert.FromBase64String(authHeader);
var lp = Encoding.Default.GetString(fromBase64String);
if (string.IsNullOrWhiteSpace(lp))
return null;
string login;
string password;
int pos = lp.IndexOf(':');
if (pos < 0)
{
login = lp;
password = string.Empty;
}
else
{
login = lp.Substring(0, pos).Trim();
password = lp.Substring(pos + 1).Trim();
}
return ValidateUser(login, password);
}
protected abstract IPrincipal ValidateUser(string userName, string password);
}
Write you own user validation logic. For example:
public class SampleBasicAuthMessageHandler : BasicAuthMessageHandler
{
protected override IPrincipal ValidateUser(string userName, string password)
{
if (string.Equals(userName, "Meziantou", StringComparison.OrdinalIgnoreCase) && password == "123456")
return new GenericPrincipal(new GenericIdentity(userName, "Basic"), new string[0]);
return null;
}
}
Finally you have to register the Handler
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new SampleBasicAuthMessageHandler());
You'll find a complete example on Github: https://github.com/meziantou/Samples/tree/master/Web%20Api%20-%20Basic%20Authentication