I have this small snippet that makes a request to a login endpoint and it works fine.
public func loginWithEmail(email: String, password: String, completionHandler: Result<String, NSError> -> Void) {
Alamofire.request(AuthenticationRouter.Login(email: email, password: password))
.validate()
.responseString { response in
completionHandler(response.result)
}
}
My question is: when the user inputs invalid credentials the server returns 403 which due to validate will generate a Result<NSError>. What is the best way to modify the error description / failure reason in this error?
Should I create my own error? Is there a way to modify it for all the requests?
Alamofire validate func snippet for reference:
public func validate<S: SequenceType where S.Generator.Element == Int>(statusCode acceptableStatusCode: S) -> Self {
return validate { _, response in
if acceptableStatusCode.contains(response.statusCode) {
return .Success
} else {
let failureReason = "Response status code was unacceptable: \(response.statusCode)"
return .Failure(Error.errorWithCode(.StatusCodeValidationFailed, failureReason: failureReason))
}
}
}
The easiest way would be to act on the error once it reaches your app code. You probably don't want to create a custom validate method that overrides the default Alamofire behavior because your case is VERY specific.
In general, you would set your acceptable status codes and use the validate(statusCodes:) method. Then in your response logic in your app code, switch on the error and create custom app representations of each case. In this case, a 403 means that the incorrect credentials were used which only your application knows. Hence, it should live in your app code, not in an Alamofire validation extension. My two cents...
Related
I'm new to GO and I'm following https://parthdesai.me/articles/2016/05/20/go-rpc-server/ to build a simple RPC server&client. In this article, it says
Benefit of this approach (HTTP) is, you can perform authentication of client easily, before allowing RPC, using any authentication method supported by HTTP.
But the example on the page doesn't seem to perform it. I have searched it on StackOverflow, and found Passing authentication details with a JSON-RPC call saying
There's two ways to accomplish what you want: either implement an HTTP-speaking io.ReadWriteCloser and use as in your example or implement an rpc.ClientCodec that does the HTTP basic auth and use in conjunction with rpc.NewClientWithCodec.
However, I still don't know how to do it. Could I have some example code (may be Basic Authentication method)?
Using transcoding and checking authentication in gRPC middleware is better for http server over gRPC using gRPC gateway.
https://cloud.google.com/endpoints/docs/grpc/transcoding
To get authorization in headers, use gRPC middleware and get from context with gRPC MD.
srv := grpc.NewServer(exampleJwtMiddleware())
func exampleJwtMiddleware() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
token, err := extractHeaderFromContext(ctx, "Authorization")
// do sometings...
return handler(ctx, req)
}
}
func extractHeaderFromContext(ctx context.Context, header string) ([]string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, ERROR_NO_HEADER_IN_REQUEST
}
foundedHeaders, ok := md[header]
if !ok {
return nil, ERROR_NO_HEADER_IN_REQUEST
}
return foundedHeaders, nil
}
When I am authorising a request, if any of the standard claims in the JWT are invalid, or if it fails for some other reason (such as the signature being incorrect), I would like to be able to see what exactly was incorrect, especially when testing. Currently, I am not able to see any message in the Unauthorized 401 response, nor in my logs.
My authentication setup (in my Application.module() function), using the auth0-jwt library.
val jwtVerifier = JWT.require(Algorithm.RSA256(getPublicKeyFromString(publicKey), null))
.withAudience("audience")
.acceptLeeway(1)
.acceptExpiresAt(5)
.build()
install(Authentication) {
jwt {
verifier(jwtVerifier)
validate { credential: JWTCredential ->
JWTPrincipal(credential.payload)
}
}
}
#OptIn(KtorExperimentalLocationsAPI::class)
install(Locations) // see http://ktor.io/features/locations.html
install(Routing) {
authenticate {
ServiceEndpoints()
}
}
I have set up an endpoint handler as follows:
fun Route.ServiceEndpoints() {
get<Paths.getData> { params ->
checkCustomClaim(context.authentication.principal(), <some other parameters here>)
//handling code here
}
}
I'll point out that checkCustomClaim() will raise an AuthorisationException (just a simple exception that I created) if the custom claim fails. I do it this way because each endpoint will be checking different information in my custom claims.
I have attempted to get logs and more information in the response with a custom status page. I am able to get the log message and response data for my AuthorisationExceptions, but not for failures in the standard claims.
install(StatusPages) {
exception<JWTVerificationException> { cause ->
log.warn("Unauthorized: ${cause.message}")
this.call.respond(
status = HttpStatusCode.Unauthorized,
message = cause.message ?: "Unauthorized"
)
}
exception<AuthorisationException> { cause ->
log.warn("Unauthorized: ${cause.message}")
this.call.respond(
status = HttpStatusCode.Unauthorized,
message = cause.message ?: "Unauthorized"
)
}
}
You can use information from a JWT diagnostics log that is written on the TRACE level.
Is there a way to cancel a specific request? I followed this to cancel request, but what if i have more than one request, how can i be sure that im not canceling the wrong request? Is there a way to cancel a specific request?
Currently it works like this:
async getEmployeeListBySearchword(searchword: string): Promise<IMember[]> {
try {
const response = await this.get(`employees/${decodeURIComponent(searchword)}`);
return JSON.parse(response['response']) as Promise<IMember[]>;
}catch(e){
if(e.responseType != "abort") {
console.log(e);
}
}
}
cancelRequest(){
if(AbortableHttpService.http){
AbortableHttpService.http['pendingRequests'].forEach(request =>{
request.abort();
});
}
}
The http setup code is wrapped into a class named AbortableHttpService, it creates the HttpClient and configures it and hides some of the boilerplate code for it.
I'm not sure if this is documented or not, but in the aurelia-http-client source code, I see that a cancel/abort function is added to the promise you receive when making a request.
So calling either response.cancel() or response.abort() should work.
I'm using Angular2 with ASP.NET Core MVC and managing manual URL navigation works fine, the server is loading my Home view with Angular2 successfully.
On user authentication, I'm setting up a session variable like this :
HttpHelper.HttpContext.Session.SetString("isLoggedIn", true.ToString() );
What I want is that after the user is in the application, if somewhat he wants to load a specific route by manually navigating to it, I want my service to call my ASP Controller to check if the user already got authenticated so that my guard allows the routing. Instead, the guard is by default set to false and I obviously get redirected to my login page.
Here is my ASP Controller method I want to call to update my IsLoggedIn value in my auth.service :
[HttpGet]
public IActionResult IsConnectedState()
{
if (!String.IsNullOrWhiteSpace(HttpHelper.HttpContext.Session.GetString("isLoggedIn")))
return Ok(true);
else
return Ok(false);
}
so that my AuthenticationGuard can call the AuthenticationService to update the boolean managing the authenticated state :
alert(this.authService.isLoggedIn);
if (!this.authService.isLoggedIn) {
this.authService.setupLoggedInState().subscribe(() => { });
}
with the following code updating the boolean in my auth.service :
setupLoggedInState() {
alert("Setting Up");
// Setting up the Http call
let lControllerAction: string = "/IsConnectedState";
let lControllerFullURL: string = this.controllerURL + lControllerAction;
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
// Call my ASP Controller IsConnectedState() method
return this.http.get(lControllerFullURL, options)
.map((res: any) => {
// Réponse reçue du WebService
let data = res.json();
alert(data);
if (data == true) {
this.isLoggedIn = true;
}
}
).catch(this.handleError);
}
When I do the authentication, and then manually navigate to an URL, my guard tells me that the boolean is indeed set to "false", and I also get the "Setting Up" when my service tries to call http.get.
It seems to bug in the middle of the method, because I never get to the breakpoint I set in my ASP Controller. I get the following error which I don't understand :
"platform-browser.umd.js:937 EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'toString' of null"
Could it be because I don't call the service at the right moment in my auth.guard ? All my others call such as authentication work with http.post without any issue, so I don't really get where the problem is coming from...
Any help would be greatly appreciated.
There's a known defect in Angular 2 RC5 that causes this error when you do a get and provide the 'Content-Type': 'application/json' header.
For a temporary workaround, add an empty string to the body property on your request options:
let options = new RequestOptions({ headers: headers, body: "" });
I'm using FOSRestBundle for my REST API and so far it has been a great tool. I use HTTP Basic Auth and in most of the cases it works just fine. However, I have problems with the bundle's exception behaviour when bad credentials are submitted. When handling exceptions (via the integrated authentication handlers or the exception mapping configuration), the bundle always gives me a response with the correct HTTP status and JSON/XML content similar to this:
{
"code": 401,
"message": "You are not authenticated"
}
This is fine, it also works when no authentication information is submitted at all. However, when submitting bad credentials (e.g. unknown username or incorrect password) I get the HTTP code 401 Bad credentials (which is fine) with an empty message body. Instead, I would have expected something similar to the JSON above.
Is it a bug or a configuration issue on my side? I would also love to know how these kinds of authentication errors are exactly handled by the bundle, since overriding the BadCredentialsException's status code in the codes section of the bundle's exception configuration section seems to be ignored.
Thanks!
Alright, after digging into the bundle's code some more, I figured it out. The problem results from the way bad credentials are handled by Symfony's HTTP Basic Authentication impementation. The 401 Bad Credentials response is a custom response created by BasicAuthenticationEntryPoint, which is called by the BasicAuthenticationListener's handle function, immediately after an AuthenticationException has been thrown in the same function. So there is no way of catching this exception with a listener:
public function handle(GetResponseEvent $event)
{
$request = $event->getRequest();
if (false === $username = $request->headers->get('PHP_AUTH_USER', false)) {
return;
}
if (null !== $token = $this->securityContext->getToken()) {
if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $username) {
return;
}
}
if (null !== $this->logger) {
$this->logger->info(sprintf('Basic Authentication Authorization header found for user "%s"', $username));
}
try {
$token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $request->headers->get('PHP_AUTH_PW'), $this->providerKey));
$this->securityContext->setToken($token);
} catch (AuthenticationException $failed) {
$this->securityContext->setToken(null);
if (null !== $this->logger) {
$this->logger->info(sprintf('Authentication request failed for user "%s": %s', $username, $failed->getMessage()));
}
if ($this->ignoreFailure) {
return;
}
$event->setResponse($this->authenticationEntryPoint->start($request, $failed));
}
}
The entry point's start function creates the custom response, with no exceptions involved:
public function start(Request $request, AuthenticationException $authException = null)
{
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
$response->setStatusCode(401, $authException ? $authException->getMessage() : null);
return $response;
}
The fist if-clause in the handle function above also explains why it works in the case of "no user credentials at all", since in that case, the listener just stops trying to authenticate the user, and therefore an exception will be thrown by Symfony's firewall listeners (not quite sure where exactly), so FOSRestBundle's AccessDeniedListener is able to catch the AuthenticationException and do its thing.
You can extend AccessDeniedListener and tell FOSRestBundle to use your own listener with the parameter %fos_rest.access_denied_listener.class%. (service definition)
parameters:
fos_rest.access_denied_listener.class: Your\Namespace\For\AccessDeniedListener
Then add an additional check for BadCredentialsException and emmit an HttpException with the desired code/message similar to the check for AuthenticationException at Line 70.