Azure AD B2C OAuth2 how to refresh access token silently - asp.net-mvc

We are using Azure AD B2C to authenticate our users in an ASP.NET MVC application with OWIN (Microsoft.Owin.Security).
We are trying to find a way to refresh silently the access token (access_token), to avoid the multiple AJAX calls we are doing to fail. Most of them are fired automatically by the components we use (DevExpress) through callbacks, so we have very little control over them.
One solution we found is the one described by Vittorio Bertocci on his blog : http://www.cloudidentity.com/blog/2016/07/25/controlling-a-web-apps-session-duration-2/
AccountController.cs
public void ForcedSignIn()
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties {RedirectUri = "/Account/AfterForcedSignIn"}, "OUR_B2C_SIGNIN_POLICY_ID");
}
_Layout.cshtml
<script>
setInterval(function() {
#if (Request.IsAuthenticated)
{
<text>
var renewUrl = "/Account/ForcedSignIn";
var element = document.getElementById("renewSession");
console.log("sending request to: " + renewUrl);
element.src = renewUrl;
</text>
}
},
1000 * 20
);
</script>
<body>
<iframe id="renewSession" style="height: 600px; width: 600px;"></iframe>
AfterForcedSignIn.cshtml
#{
Layout = null;
}
However, the call to https://login.microsoftonline.com is failing because it can’t be embedded in an iFrame (even if we specify “prompt” parameter to “none”).
See the message we get in Edge for example :
Is there a way to avoid this issue (are we missing a parameter when calling the login page, or something else) ? Or is it another solution that exists ?
Thanks,
Léo

If you use MSAL they have an AzquireTokenSilently method that you could use to refresh the user
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet

Related

How can I use the branded Google, Facebook, etc. login buttons with OAuth in my ASP .Net Core (MVC) application?

The ASP .Net Core MVC project template has some functionality built in to support Individual User Accounts, including OAuth for Google, Facebook, and Twitter. The standard buttons are just plain, lame buttons however, and I would much rather use the branded buttons from Facebook, Google, Twitter, et al.
I understand that there are basically two apporaches to OAuth: Front-end authentication, and back-channel authentication, where the latter involves the server making a server-provider (provider being Facebook, Google, or whomever) call, passing the application's key and requested permissions, with a callback to the server from the provider when authentication is complete, whereas the former is a direct call from the front-end (e.g. Javascript in the browser) to the provider, utilizing controls that are built from CSS and Javascript provided by the individual providers.
What I would like to do is something of a hybrid approach-- I would like to use those nice, branded buttons with their imagery (i.e., the Facebook button shows the users picture if they're logged in; Google's might as well), but I want to pass the authentication information back to my back-end (so I can manage the user's permissions to do stuff on my website.) Is this possible? If so, what mechanism would I use to do it, and it it possible to integrate that approach with all the stuff that's already built into the MVC template and the framework?
One interesting thing that I read in the official docs from facebook on the use of their control suggested that you should protect your application's key, to prevent someone else from stealing it and misusing it, presumably impersonating you or your site for nefarious purposes. Obviously, that would be one huge advantage to using back-channel authentication, because nowhere in that stream, from the user's perspective, is your application key ever seen; when using the javascript approach documented by the provider's branded buttons, you end up having to store your application key right there in plain text in the javascript, and there doesn't seem to be a way around that, which seems odd, given that they tell you that is precisely what you DON'T want to be doing.
So, to summarize:
If I use the javascript approach to get the branded buttons, how do I protect my application key from being scraped and misused by a third party?
How can I get the authentication information that comes back from Facebook or other providers from the javascript (branded) controls, and then feed that information to my backend? Is it possible to wire that into the existing framework stuff that handles authentication and authorization, or will I then have to home-brew everything?
Those login buttons from the various providers seem simple enough, but in reality they appear to be a lot more than just image buttons-- There seems to be a lot of magic going on in the javascript and CSS, but it seems like this should be possible. Almost every site that I visit that has "Login with Google" buttons and the like is using the nice, pretty branded buttons, and I'm sure the sites are not written entirely in Javascript. Thanks for any help!
Thanks for the early replies! A quick update that I have gotten the Google Sign-In button to work, and I can read a response back from Google and can thus see that sign-in attempt was successful. **Now what I'm looking to do is to somehow pass that information back to my server-side application so I can sign the user in to my website.** Here is the Javascript that have now. The function *initGoogleButton*, if present, is apparently called by Google's code to allow me to customize the button and set various parameters.
<script>
function onSuccess(googleUser) {
id = googleUser.getId();
scopes = googleUser.getGrantedScopes();
alert("Id: " + id + ", Scopes: " + scopes);
profile = googleUser.getBasicProfile();
id = profile.getId();
name = profile.getName();
givenName = profile.getGivenName();
familyName = profile.getFamilyName();
email = profile.getEmail();
image = profile.getImageUrl();
alert("Id: " + id + ", name: " + name + ", Given: " + givenna + " family: " + familyName + ", email: " + email + ", image: " + getImageUrl);
document.getElementById('externalLoginForm').Submit();
console.log('Logged in as: ' + googleUser.getBasicProfile().getName());
alert("Success!");
}
function onFailure() {
alert("Failure!");
}
function renderGoogleButton() {
gapi.signin2.render('my-signin2', {
scope: 'profile email',
width: 250,
height: 40,
longtitle: true,
theme: 'dark',
onsuccess: onSuccess,
onfailure: onFailure,
});
}
function onGoogleInit(authObj) {
alert("Signed in == " + authObj.isSignedIn.get());
alert("onGoogleInit");
if (authObj.isSignedIn.get()) {
//authObj.signOut();
}
renderGoogleButton();
}
function onGoogleInitError(authError) {
alert("Auth Error!");
alert(authError.error);
}
function initGoogleButton() {
alert("Init");
gapi.load('auth2', function () {
promise = gapi.auth2.init({
client_id: '12345678987654321-blahblahblahMyAppId12345.apps.googleusercontent.com',
//scope: "email profile",
//fetch_basic_profile: true,
//redirect_uri: 'https://localhost:44359/Account/Callback',
}).then(onGoogleInit, onGoogleInitError);
});
}
</script>
<script src="https://apis.google.com/js/platform.js?onload=initGoogleButton" async defer></script>
So you'll note the commented out code there where I was trying to provide a callback for Google to send the authentication message back to my controller. That never worked. My Alert() calls though were properly showing me the Id and scopes that Google gave back.
When doing the built-in sign-in functionality in the MVC template, you have a button on a form that invokes a backend call in your controller that does a few async calls to sign you in with the authenticated Google account credentials and, if you don't have an account on the website, invites you to register. Those calls are using a lot of framework stuff for authentication and I'd like to leverage it.
using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { id = "externalLoginForm" }))
{
#Html.AntiForgeryToken()
<div id="socialLoginList">
<p>
#* Google login button *#
<div class="g-signin2" data-height="40" data-width="250" data-theme="dark" data-redirecturi="https://localhost:44359/signin-google"></div><br />
<script src="https://apis.google.com/js/platform.js" async defer></script>
#* Facebook login button *#
<div class="fb-login-button" data-width="250" data-size="large" data-button-type="login_with" data-auto-logout-link="false" data-use-continue-as="true"></div><br />
#* Configured social media logins *#
#foreach (AuthenticationDescription p in loginProviders)
{
<button type="submit" class="btn btn-default" id="#p.AuthenticationType" name="provider" value="#p.AuthenticationType" title="Log in using your #p.Caption account">#p.AuthenticationType</button>
}
</p>
</div>
}
Then, in the external login action in the controller...
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
This, in turn causes an ExecuteResult on the ChallengeResult, which is what takes you to the Google page, which is fed the Account/ExternalLoginCallback as its callback point.
It is the callback that then does all the stuff to either sign you in to the website, or invite you to register if you don't have a local account. A lot of this is using stuff from the OWinContext or other things I don't seem to have access to. Obviously I could home-brew my own solution, but I'd prefer if there was a nice, clean way to just wire into the rest of this stuff that's already done. After all, all I wanted are those nice, pretty branded buttons. :) I'm totally fine with the way that the framework is doing the rest of this.

Function to request user claims and token from Identity Server?

What is the best way to retrieve user claims and tokens after logging into identity server?
I have a .Net Core MVC web app. I redirect to an identity server, log in, then redirect back to my web app.
From my app I would then like to request user claims and token from identity server.
Is there a method call to do this?
I have gone through the tutorials and searched around and found libraries (oidc-client-js, oidc.usermanager for javascript that handles this. OidcClient for native c# apps) that handle this behavior, but not for c# web app - so I realize maybe what I'm asking for is not the convention but I'd still appreciate any insight on this.
Edit:
To elaborate, in the c# web app tutorials, specifically tutorial #3, I fail to see how claims and token information become retrievable from from figure 1 to figure 2.
Furthermore, I'm trying to call and handle these objects in c#, not html.
Figure 1
Figure 2
Thank you
In controller , you can retire claims like
var claims = User.Claims;
Get the access token/id token/refresh token :
var accessToken = HttpContext.GetTokenAsync("access_token").Result;
var idToken = HttpContext.GetTokenAsync("id_token").Result;
var refreshToken = HttpContext.GetTokenAsync("refresh_token").Result;
To achieve that , you need register your OIDC middleware inside ConfigureServices as follows (making sure to set SaveTokens to true) :
.AddOpenIdConnect(options => {
// Set all your OIDC options...
// and then set SaveTokens to save tokens to the AuthenticationProperties
options.SaveTokens = true;
});
In mvc razor page , you can get the information :
#using Microsoft.AspNetCore.Authentication
#{
ViewData["Title"] = "Home Page";
}
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
<dt>access token</dt>
<dd>#await ViewContext.HttpContext.GetTokenAsync("access_token")</dd>
<dt>ID token</dt>
<dd>#await ViewContext.HttpContext.GetTokenAsync("id_token")</dd>
<dt>refresh token</dt>
<dd>#await ViewContext.HttpContext.GetTokenAsync("refresh_token")</dd>

.NET Core MVC/Azure AD - 302 Found when making an ajax request

I am using Azure AD along with asp.net core mvc. The following code is the same with a default MVC project generated with Work or School Accounts authentication.
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Everything works just fine for the most time. The app is basically a notepad. A user logs in and adds notes/tasks. Everything after logging in is done using ajax requests. After some time the app stops working because there is a need for authentication again. Note that if I refresh the page everything is working again.
Am I doing this right? Am I missing something or this kind of use case is not supported.
Should I just create a javascript function that will auto refresh the page after some time?
Should I just create a javascript function that will auto refresh the page after some time?
You could try to create a hidden iframe in all the templates used by the Web App to make automatic calls to a MVC controller method that forces a call to renew the authentication data on a regular basis.
This is achieved very easily by configuring an automatic javascript process in the front-end executed in a loop on a regular basis of 45'. This value could be configured or extracted from a configuration file too. The only key condition is that it must be less than one hour.
Here is the simplified example code related to MVC Controller:
/* Action method, inside "Account" controller class, to force renewal of user authentication session */
public void ForceSignIn()
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
And here is the simplified example HTML and javascript code employed to call silently in a hidden iframe to MVC Controller:
<iframe id="renewSession" hidden></iframe>
<script>
setInterval( function ()
{ #if (Request.IsAuthenticated) {
<text>
var renewUrl = "/Account/ForceSignIn";
var element = document.getElementById("renewSession");
element.src = renewUrl;
</text>
}
},
1000*60*45
);
</script>
For more details, you could refer to this article which get the similar situation with you.
I found a simple solution by accident. My goal was to hit a check endpoint every minute and If I get a 302 status code I would redirect the user to the authentication page.
public IActionResult Check()
{
return Ok(new { });
}
I left the developer tools open and noticed that every 30 mins I get a bigger response.
And this actually refreshes the cookie and as a result there is no need to redirect the user.
So to sum up someone needs to do this check every 40-50 minutes because the expiration is set to ~1 hour by default.

MVC Site Authentication application only

I need to limit access for my MVC site and accept only requests that come from another site.
Inside a main site i have a link that will re-direct in child site users that are already authenticated.
So i would like to process this request and authorize users if they clicked in this link.
I'm thinking to share a token in both applications, but when i re-direct user i can't attach this token to the request (true ?) but only in query string.
My query string have alredy the userId and two integer values.
Here is my code from Main site
public void Redirect(HttpResponse response)
{
var userId = HttpUtility.UrlEncode(_encryptor.Encrypt(_userId.ToString(CultureInfo.InvariantCulture)));
var year = HttpUtility.UrlEncode(_encryptor.Encrypt(_compilationYear.ToString(CultureInfo.InvariantCulture)));
var month = HttpUtility.UrlEncode(_encryptor.Encrypt(_compilationMonth.ToString(CultureInfo.InvariantCulture)));
const string baseUrl = "http://localhost:63415/Home/Index"; //external site
response.Redirect(String.Format("{0}?userId={1}&year={2}&month={3}", baseUrl, userId, year, month));
}
and in child site side
public HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//if request come from the main site
var encUserId = Request.QueryString["userId"];
var encYear = Request.QueryString["year"];
var encMonth = Request.QueryString["month"];
//else show unauthorized page error
}
}
What about set cookie with token ?
I can change code but not in aspx where i have
<foo:MyButton Alt="redirect"
Enabled="true" Height="22" Id="btn"
OnClickJs="Foo()" runat="server"
/>
Now i'm thinking to perform post action via jQuery inside Foo() method... but i have the error
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Sharing a token could be a good solution in case that this token is dynamic and not static (new token for each redirect) because otherwise once somebody gets this token he will be able to share it and access yous site directly.
About sharing token there are 2 ways that I am aware of:
Sharing the same storage: your main application stores a token and a user identity somewhere in database and your secondary MVC application can retrieve this token from DB and invalidate it.
Implementing a common library that will be used for encryption\decryption of user data: Once user clicks on a link in your main site his credentials are encrypted into a token, this token is posted to your MVC site which is going to use the same library to decrypt the token and extract user credentials from there. If you are going to use this approach you will need to use some strong encryption algorithm (probably symmetric one that uses the same key for encryption\decryption because you probably don't want to deal with key distribution and using a public key to encrypt a message is not a solution in your case). You also have to insure that algorithm that you are using doesn't generate the same token for the same user every time.
when i re-direct user i can't attach this token to the request (true ?)
If you are using a redirect from server you are right, but you could take a different approach (Assuming that your main site is MVC):
When user clicks on a link (your link should have a "_blank" target so it could be opened in a new browser window in your main site you go to an action in your main site that generates a token and passes it as a model to it's view:
public class RedirectModel
{
public string Token { get; set; }
public string Url { get; set; }
}
public ActionResult Redirect()
{
var model = new RedirectModel{
Token = GenerateToken(),
Url = //url of your secondary site login action
}
return View(model);
}
View has a form that is going to be submitted on document load:
#model RedirectModel
<html>
<body onload="document.redirect.submit()">
<form method="POST" action="#Model.Url" novalidate autocomplete="off" name="redirect">
<input type="hidden" name="token" value="#Model.Token">
</form>
</body>
</html>
Of course the login action of your secondary MVC site has to decrypt a token and extract user credentials from it.

How to get OAuth working on MVC4 mobile

I have been struggling to get OAuth to work on my MVC4 mobile application.
I basically
Created an MVC4 Mobile application
Created an MVC4 Web application
Copied the AccountController, AccountModel, InitializeSimpleMembershipAttribute and all account views into my MVC4 Mobile application
Enabled Google and Facebook OAuth providers
I also ensured RoleManager was initialized according to simplemembershipprovider-not-working, although I do not think that was important. ( I needed it for some role-based security I was testing)
Disabled ajax ( I think) by setting data-ajax="false":
using (Html.BeginForm("ExternalLogin", "Account",new RouteValueDictionary { { "ReturnUrl", ViewBag.ReturnUrl } }, FormMethod.Post, new Dictionary<string, object> { { "data-ajax", false } })) (This did not seem to have any effect on the page - so I may be doing something wrong here...)
I can get to the LogIn view /Account/Login, and when I click the google button, the debugger breaks into public ActionResult ExternalLogin(string provider, string returnUrl)
However - the public ActionResult ExternalLoginCallback(string returnUrl) is never hit.
Visually, I get the jquery mobile "page loading" animation - then I get "Error Loading Page"
I have two questions:
How can I get more information when I try to figure out what happens?
How can I get OAuth working on my MVC4 mobile site?
BTW: Both sites target .Net4.0
OK - so I figured out the answer - the cuplrit was indeed the ajax in jQuery mobile.
I modified my _layout.cshtml so that it can render a custom script after loading jQuery, but before loading jQuery mobile:
#Scripts.Render("~/bundles/jquery","~/scripts/RemoveHashFromWindowLocation")
#RenderSection("beforeJqueryMobile", required: false);
#Scripts.Render( "~/bundles/jquerymobile")
#RenderSection("scripts", required: false)
Then I modified my Login.cshtml so that it contains the section:
#section BeforeJqueryMobile {
#Scripts.Render("~/scripts/disable-ajax.js")
}
And finally, I added the following to my scripts folder:
disable-ajax.js:
$(document).bind("mobileinit", function () {
$.mobile.ajaxEnabled = false;
});
RemoveHashFromWindowLocation.js: (Thanks to Facebook Oauth Login With Jquery Mobile #_=_)
if (window.location.hash == "#_=_")
window.location.hash = "";
Voila - OAuth working with jQuery mobile. :-)
The above answer from espenalb does work if your site loads directly to the login page, however if you have say a home page and then and a link to the login page(which uses the same layout), you will get an error loading page when you click one of the social login buttons.
To fix this add
<script>
$('#login-link').live("click", function () {
$.mobile.ajaxEnabled = false;
});
</script>
to the bottom of your mobile layout page, this will attach an event handler that will disable ajax to the login link.

Resources