Logout from external login service (Gmail, facebook) using oauth - asp.net-mvc-4

I have an ASP.NET MVC 4 application that allows users to login with external service like Gmail.
So far, the user is able to login and navigate inside the application. But The problem is in logout. I have a button to logout that request call the controller action LogOff() inside my AccountController. Inside that method, how can I logout if the user is authenticated via oauth?
With a local account, I use:
public ActionResult LogOff()
{
WebSecurity.Logout();
return RedirectToAction("Login", "Account");
}
But with oauth I don't see anything similar...
I think I need to clear some kind of cookie but I don't know how...

Based on this, I implemented the following client-side solution (I'm asking previously if the user want to logout also in the provider):
//get accountType, accessToken, redirectUrl and clientID
var accountType = ...;
var accessToken = ...;
var redirectUrl = ...;
var clientID = ...;
$("#logoutConfirmButton").on('click', function () {
externalLogout();
});
function externalLogout() {
var url, params;
if (accountType== "facebook") {
url = "https://www.facebook.com/logout.php";
params = {
next: redirectUrl,
access_token: encodeURIComponent(accessToken)
};
performCallLogout(url, params, accountType);
} else if (accountType== "google") {
url = "https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout";
params = {
next: redirectUrl
}
performCallLogout(url, params, accountType);
} else if (accountType == "microsoft") {
url = "https://login.live.com/oauth20_logout.srf";
params = {
clientId: clientID,
redirectUrl: redirectUrl
}
performCallLogout(url, params, accountType);
}
}
function performCallLogout(url, params, accountType) {
if (accountType == "facebook") {
window.location.href = url + "?next=" + params.next + "&access_token=" + params.access_token;
} else if (accountType == "google") {
window.location.href = url + "?continue=" + params.next;
} else if (accountType == "microsoft") {
window.location.href = url + "?client_id=" + params.clientId + "&redirect_url=" + params.redirectUrl;
}
}
Hope this help someone.

WebSecurity.Logout(); will log out the user even if they authenticated through OAuth.
If you want to be sure the token does not persist after logout you can call
Session.Remove("facebooktoken"); //Facebook example
The information is from this webpage. Some more details worth reading on there too.

Sounds like you want to log the user out of the source authenticating site? Only the authenticating site can delete/modify its cookies.
The solution will be to redirect the user to the logout page for the authenticating site, or use an API script to log the user out (if one exists for that site.) You could use a form with the "target" attribute to open a new window if you don't want the main browser window to redirect.
FaceBook, for example, has an API call:
FB.logout(function(response) {
// user is now logged out
});
The MVC FaceBook client has a method GetLogoutUrl, too, which returns a URL you could use on the server side.

Related

User.Identity.IsAuthenticated AND _signInManager.IsSignedIn(User) return always null / fasle IN MVC CORE 6 2022

I have a new asp.net MVC core 6 application .try to authenticate users ( not by using Identity scaffolding ) .. however the the SignInmanger is always return False
Login function
programe.cs
Full code snippet for login :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM loginvm)
{ // this wil return view model
if (!ModelState.IsValid)
{
return View(loginvm);
}
var user = await _userManager.FindByEmailAsync(loginvm.Username);
if (user != null)
{
// if we have user let us check the password
var checkpsssword = await _userManager.CheckPasswordAsync(user, loginvm.Password);
if (checkpsssword)
{
var letUserLoginIn = await _signInManager.PasswordSignInAsync(user, loginvm.Password, false, false);
if (letUserLoginIn.Succeeded)
{
var tempo = User.Identity.IsAuthenticated;
var isok = _signInManager.IsSignedIn(User);
ViewBag.tempo=tempo;
ViewBag.isok = isok;
return RedirectToAction("index", "Movie");
}
ModelState.AddModelError("Error","can login innnnn");
TempData["Error"] = "Password is not correct! !";
return View(loginvm);
}
else
{
// password wrong
TempData["Error"] = "Password is not correct! !";
}
}
TempData["Error"] = "no user found ya mozznoz!";
return View(loginvm);//STRONGLY TYPED VIEW
}
One part #Kevin have mentioned above, and another part was the missing of authentication mechanism register.
It should be something like builder.Services.AddAuthentication(opts => opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
AddAuthentication part add all the necessary middlewares and config to setup authentication process. Here, we specify DefaultScheme as CookieAuthenticationDefaults.AuthenticationScheme
AddCookie tell asp.net Core that we want to store the login information in cookie, therefore, a response that tell client to save a cookie with pre-defined information was sent(and the name for that authentication mechanism of choice was default to CookieAuthenticationDefaults.AuthenticationScheme).
For every subsequent requests, the cookie was included then server know, we already logged in

OAuth with KeyCloak in Ktor : Is it supposed to work like this?

I tried to set up a working Oauth2 authorization via Keycloak in a Ktor web server. The expected flow would be sending a request from the web server to keycloak and logging in on the given UI, then Keycloak sends back a code that can be used to receive a token. Like here
First I did it based on the examples in Ktor's documentation. Oauth It worked fine until it got to the point where I had to receive the token, then it just gave me HTTP status 401. Even though the curl command works properly. Then I tried an example project I found on GitHub , I managed to make it work by building my own HTTP request and sending it to the Keycloak server to receive the token, but is it supposed to work like this?
I have multiple questions regarding this.
Is this function supposed to handle both authorization and getting the token?
authenticate(keycloakOAuth) {
get("/oauth") {
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
call.respondText("Access Token = ${principal?.accessToken}")
}
}
I think my configuration is correct, since I can receive the authorization, just not the token.
const val KEYCLOAK_ADDRESS = "**"
val keycloakProvider = OAuthServerSettings.OAuth2ServerSettings(
name = "keycloak",
authorizeUrl = "$KEYCLOAK_ADDRESS/auth/realms/production/protocol/openid-connect/auth",
accessTokenUrl = "$KEYCLOAK_ADDRESS/auth/realms/production/protocol/openid-connect/token",
clientId = "**",
clientSecret = "**",
accessTokenRequiresBasicAuth = false,
requestMethod = HttpMethod.Post, // must POST to token endpoint
defaultScopes = listOf("roles")
)
const val keycloakOAuth = "keycloakOAuth"
install(Authentication) {
oauth(keycloakOAuth) {
client = HttpClient(Apache)
providerLookup = { keycloakProvider }
urlProvider = { "http://localhost:8080/token" }
}
}
There is this /token route I made with a built HTTP request, this one manages to get the token, but it feels like a hack.
get("/token"){
var grantType = "authorization_code"
val code = call.request.queryParameters["code"]
val requestBody = "grant_type=${grantType}&" +
"client_id=${keycloakProvider.clientId}&" +
"client_secret=${keycloakProvider.clientSecret}&" +
"code=${code.toString()}&" +
"redirect_uri=http://localhost:8080/token"
val tokenResponse = httpClient.post<HttpResponse>(keycloakProvider.accessTokenUrl) {
headers {
append("Content-Type","application/x-www-form-urlencoded")
}
body = requestBody
}
call.respondText("Access Token = ${tokenResponse.readText()}")
}
TL;DR: I can log in via Keycloak fine, but trying to get an access_token gives me 401. Is the authenticate function in ktor supposed to handle that too?
The answer to your first question: it will be used for both if that route corresponds to the redirect URI returned in urlProvider lambda.
The overall process is the following:
A user opens http://localhost:7777/login (any route under authenticate) in a browser
Ktor makes a redirect to authorizeUrl passing necessary parameters
The User logs in through Keycloak UI
Keycloak redirects the user to the redirect URI provided by urlProvider lambda passing parameters required for acquiring an access token
Ktor makes a request to the token URL and executes the routing handler that corresponds to the redirect URI (http://localhost:7777/callback in the example).
In the handler you have access to the OAuthAccessTokenResponse object that has properties for an access token, refresh token and any other parameters returned from Keycloak.
Here is the code for the working example:
val provider = OAuthServerSettings.OAuth2ServerSettings(
name = "keycloak",
authorizeUrl = "http://localhost:8080/auth/realms/master/protocol/openid-connect/auth",
accessTokenUrl = "http://localhost:8080/auth/realms/$realm/protocol/openid-connect/token",
clientId = clientId,
clientSecret = clientSecret,
requestMethod = HttpMethod.Post // The GET HTTP method is not supported for this provider
)
fun main() {
embeddedServer(Netty, port = 7777) {
install(Authentication) {
oauth("keycloak_oauth") {
client = HttpClient(Apache)
providerLookup = { provider }
// The URL should match "Valid Redirect URIs" pattern in Keycloak client settings
urlProvider = { "http://localhost:7777/callback" }
}
}
routing {
authenticate("keycloak_oauth") {
get("login") {
// The user will be redirected to authorizeUrl first
}
route("/callback") {
// This handler will be executed after making a request to a provider's token URL.
handle {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal != null) {
val response = principal as OAuthAccessTokenResponse.OAuth2
call.respondText { "Access token: ${response.accessToken}" }
} else {
call.respondText { "NO principal" }
}
}
}
}
}
}.start(wait = false)
}

Re-request fb-dialog if User denies scope - Identityserver4

Facebook docs give this javascript code
FB.login(
function(response) {
console.log(response);
},
{
scope: 'email',
auth_type: 'rerequest'
});
Is it possible to implement above functionality in my StartUp.cs instead?
app.UseFacebookAuthentication(new FacebookOptions
{
AuthenticationScheme = "Facebook",
DisplayName = "Facebook",
SignInScheme =
IdentityServerConstants.ExternalCookieAuthenticationScheme,
AppId = "***",
AppSecret = "***",
Events = new OAuthEvents
{
OnRemoteFailure = context =>
{
context.HandleResponse();
context.Response.Redirect("/Account/Login");
return Task.FromResult(0);
}
}
});
A little late but I was having the same question and maybe my solution will help others in future.
First: To force the consent page at facebook you need to add auth_type=rerequest to the query (handling revoked permissions).
Second: A single revoked permission will not raise an error. It will go the normal way but without the requested scope/claims. So OnRemoteFailure will not be called in this case. OnRemoteFailure will only be called if the consent page is canceled by the user at all.
Now you can do this:
Check the returned claims at callback method.
If the email claim is missing (or any other requested claim that is important for you) redirect the user to a custom page that handles this case. Technically you could redirect the user direct back to facebook but this is not recommended. Facebook expects from you that you explain the user exactly why you need the requested scope (handling revoked permissions).
Customize the method that initiates the sign-in process so that you know when to add auth_type=rerequest, i.e. when the user in step 2) pressed a button like "try again".
Add auth_type=rerequest to AuthenticationProperties
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("ExternalLoginCallback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", provider },
// if user wants to try again
{ "auth_type", "rerequest" }
}
};
return Challenge(props, provider);
Implement OnRedirectToAuthorizationEndpoint to manipulate the default challenge redirect uri
using Microsoft.AspNetCore.WebUtilities;
...
options.Events = new OAuthEvents
{
OnRemoteFailure = ...
OnRedirectToAuthorizationEndpoint = context =>
{
if (context.Properties.Items.ContainsKey("auth_type"))
{
context.RedirectUri = QueryHelpers.AddQueryString(context.RedirectUri, "auth_type", context.Properties.Items["auth_type"]);
}
context.Response.Redirect(context.RedirectUri);
return Task.FromResult(0);
}
};
Now the consent page shows up again when needed.
Hope this helps.

Chrome extensions: issue with identity.launchWebAuthFlow

I'm tring to login via my own service. This is what I have now:
manifest.json
"background": {
"scripts": ["background.js"]
}
background.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.create({
url: 'index.html'
});
});
index.html is where all the extension's logic resides. Here I have a function that starts authentication process:
function goLogin(callback)
{
var redirectUrl = chrome.identity.getRedirectURL('receiveToken');
chrome.identity.launchWebAuthFlow({
url: 'http://todolist.dev/app_dev.php/login?response_type=token&redirect_url=' + redirectUrl,
interactive: true
}, function(redirectUrl) {
if (!redirectUrl) {
return;
}
// Get an access token from the url and save it in localStorage
var queryString = decodeURIComponent(redirectUrl.substr(redirectUrl.indexOf('?') + 1));
var params = queryString.split('&');
var accessToken = null;
for (var i = 0; i < params.length; i++) {
params[i] = params[i].split('=');
if (params[i][0] == 'access_token') {
accessToken = params[i][1];
break;
}
}
localStorage.setItem('accessToken', accessToken);
callback();
});
}
The problem is that the popup with the service's login page sometimes doesn't open or opens and closes automatically with the response that the user didn't approve access. Sometimes when the popup opens and I try to login with wrong credentials several times, the popup closes automatically as well (with the same "you didn't approve access" response). In the backend I don't have any restrictions to a number of login attempts.
In the backend I have a FOSUserBundle with overridden AuthenticationSuccessHandler (it does what the default success handler does + returns an access token).

How can I have a seperate login page using Durandal that has a different layout then the shell?

I've read through Durandal login page redirect pattern wow, lots of code to do what I'd think would be pretty simple.
I've also read through https://groups.google.com/forum/#!topic/durandaljs/RdGpwIm1oOU as I'd like the login page to have a simple logo with a login form, but I'd also like routing for a registration and about page as well. The rest of my site will have a menu, header, etc which I don't want to show until the user is logged in. Also, I'm not sure how this approach would update when the user logs in.
Another code example that almost does what I want to do: https://github.com/Useful-Software-Solutions-Ltd/Durandal451/blob/master/Durandal451v2/App/global/session.js
So, what should I do? Is there an official way to do this? There seems to be a mish mash of things out there that people have tried. I would think this would be a really common occurrence but couldn't find anything on the main docs.
I'm not sure this is the simplest way, but this is what I got
you will need to add some extra function after app.start() is triggered.
main.js
var auth = require('authentication'); // Authentication module
app.start().then(function()
{
// This function will wait for the promise
auth.init().then(function(data)
{
// When successfully authenticate, set the root to shell
app.setRoot('views/shell');
}
});
authentication.js
define(function(require)
{
var app = require('durandal/app');
return {
init: function()
{
// Initialize authentication...
return system.defer(function(dfd)
{
// Check if user is authenticate or if there has stored token
var isAuthenticate = someOtherFunctiontoCheck();
if (isAuthenticate)
{
dfd.resolve(true); // return promise
}
else
{
// When not authenticate, set root to login page
app.setRoot('views/login');
}
}
}
};
});
good luck! :)
UPDATE
login.js
define(function(require)
{
var ko = require('knockout');
var auth = require('authentication');
var username = ko.observable();
var password = ko.observable();
return {
username: username,
password: password,
submitForm: function()
{
// Do a login, if success, auth module will take care of it
// and here will take of the error
auth.login(username(), password()).error(function()
{
// notify user about the error (e.g invalid credentials)
});
}
};
});
Authentication.js
define(function(require)
{
var app = require('durandal/app');
return {
init: function()
{
// Initialize authentication...
return system.defer(function(dfd)
{
// Check if user is authenticate or if there has stored token
var isAuthenticate = someOtherFunctiontoCheck();
if (isAuthenticate)
{
dfd.resolve(true); // return promise
}
else
{
// When not authenticate, set root to login page
app.setRoot('views/login');
}
}
},
login: function(username, password)
{
// do authenticate for login credentials (e.g for retrieve auth token)
return $.ajax({
url : 'api/login',
type : 'POST',
data : {
username: username,
password: password
}
}).then(function(token){
// on success, stored token and set root to shell
functionToStoreToken(token);
// Set root to shell
app.setRoot('views/shell');
});
}
};
});