Working on an app that will log internal users through AD and external users through B2C. We're using openid. The login process works great for both. Logging out of AD works fine. Logging out of B2C hangs up. Here's the method I'm using to log out:
[Authorize]
public IActionResult LogoutAsync()
{
var scheme = User.Claims.FirstOrDefault(c => c.Type == ".AuthScheme").Value;
return new SignOutResult(new[] { CookieAuthenticationDefaults.AuthenticationScheme, scheme });
}
I get https://localhost:44320/signout-oidc?state=XXXXXXXXXXXXXXXXXXXXX in the URL. Any clues as to what is happening??
Is my only option to use the logoff endpoint, like this (obviously with my information and redirect)?
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{policy}/oauth2/v2.0/logout?post_logout_redirect_uri=https%3A%2F%2Fjwt.ms%2F]
I had the same issue and it was because of missing support of Razor pages. The default Microsoft.Identity.Web.UI SignOut action uses /Account/SignedOut Razor page as callback URL.
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
I added Razor support in my ASP.NET Core web app, and it fixed the issue:
services.AddRazorPages();
and
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
Alternatively, you can use your own logout endpoint, which can use any action as the signed out callback URL.
Related
I have Blazor Server hooked up with Azure B2C Cookie Auth.
services.AddAuthentication(AzureADB2CDefaults.CookieScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
services.Configure<CookieAuthenticationOptions>(
AzureADB2CDefaults.CookieScheme, options =>
{
options.Cookie.Name = "MyCookieName";
});
I'm mapping Controllers so I have Controller Endpoints to hit:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
In my Blazor Server _Host I simply am using the built in Authorize attirbute:
#attribute [Microsoft.AspNetCore.Authorization.Authorize]
On load, if the user isn't Authorized, it will redirect to B2C Sign in without issue. I can then sign in and it directs me back to the App.
However, what I can't figure out is after login how to redirect to a specific controller/action endpoint prior something like: /api/auth/mynewuser This action would then do some verification and redirect back to "/" allowing the user to continue using the Blazor app.
Given my out of the box setup pretty much... how to achieve this?
edit
Code:
https://github.com/aherrick/BlazorServerB2C
Goal is to hit this endpoint after every login/sign up:
https://github.com/aherrick/BlazorServerB2C/blob/master/BlazorServerB2C/Controllers/AuthorizeController.cs
When you sign in using Azure AD B2C, the B2C service sends a token to the "redirect_uri" .You can mention the Redirect URL used in the authentication route to a specific controller/action endpoint dynamically. This helps the app to redirect to the controller when authentication is successful.
Also, You can easily handle user is logged in or not by implementing AuthorizeView component or [Authorize] attribute.
I looked into your code Authorizationcontroller and the newuser action which is under construction.
If you can trigger on the razor page, you can use the following:
#page "/YourPageName"
#inject NavigationManager NavigationManager
<h1>xxx</h1>
.
.
.
#code {
void MethodToTriggerUrl()
{
NavigationManager.NavigateTo("PageToRedirect");
}
}
Also, please refer MS Document.
My ASP.NET Core 2.2 MVC app is using Azure AD to authenticate users. In startup.cs I added Azure AD:
services
.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.NameClaimType = "preferred_username";
});
Authorization is done by a policy check:
services.AddAuthorization(options =>
{
options.AddPolicy("AdministratorOnly", policy => {
policy.RequireClaim("groups", adminGroupId);
});
});
In appsettings.json I have all the required configuration:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "662f1be2-...",
"ClientId": "30eb6c27-...",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc"
}
If I try to access a controller decorated with [Authorize(Policy = "AdministratorOnly")] it will redirect to Azure AD and handle the login process and eventually redirect back to my app.
Questions:
Is there a prescribed to kick off the login process (preferably in a popup window)? Where do I get the URL from to redirect to?
How can I provide a button to let users logout again? If I redirect to "/signout-oidc" I'm getting signed out but I end up on a blank page.
Documentation explains it all. It is backed by a solid source code repository.
In general, the AzureAD middleware is taking care about redirection to Azure AD when the user is not authenticated. This is checked every time user ends up on action decorated with [Authorize] attribute.
As for Logout - if you use the oidc-logout, check out the sample and explanations here.
This has been baffling me for hours now, so I have been trying to get EasyAuth working using different providers.
I am using this on Azure Functions, so let's say my function address is
https://xxx.azurewebsites.net
If I want to login into the service using a Google account I send my post request along with token received from Google to the following address
https://xxx.azurewebsites.net/.auth/login/google
This gives me a converted token back.
However if I do the same thing with a Microsoft account using the following details
Request Body:
{ "access_token": "token-string-value" }
Endpoint:
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
It gives me the following error instead of a converted token
401 Unauthorized You do not have permission to view this directory or page.
--
I am using Msal JavaScript library to get my authentication token. Also I am testing these in Postman which makes it easy to understand what the problem is before I deal with the code and other stuff.
-- Update 1.0
This does seem like a bug, as even if I try to navigate to the
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
It shows me the following
This URL works for other providers, Google, Facebook and Twitter. For all of them it redirects the user to the provider's login page.
According to the error page and the address bar contents, the client doesn't exist which could be referring to the application created on Azure to allow my website access the API. But everything has been setup correctly.
It would be helpful if someone from Azure We App Services can take a look at this.
I have created the Application and added the application ID and Secret int eh App Services page.
-- Update 2.0
So after hours of investigation, I managed to get the URL working, shockingly it was due to wrong information given on Azure portal. The link in Authorization and Authentication section of App Service is pointing to a new platform to register applications, which is purely for Azure AD based users.
For the external users to be able to login the application need to be registered in the following portal
https://apps.dev.microsoft.com
After registering the application here, and added the details in the App Service blade, the URL to EasyAuth is working.
However this doesn't resolve my issue. I still need a JavaScript library that gives me valid token which I can pass to EasyAuth endpoint.
Strangely the token taken from MSAL is not valid for Microsoft account. It just gives me the same error that my access is unauthorised. This means I probably need to use a different library to get a different token. I'd appreciate it if still someone can help me with this.
Below is a short sample code I am using to retrieve token and pass it to another function n which call EasyAuth endpoint and post the token along.
var applicationConfig = {
clientID: "xxxx-xxx-xxxx-xxxx",
authority: "https://login.microsoftonline.com/9fc1061d-5e26-4fd5-807e-bd969d857223",
graphScopes: ["user.read"],
graphEndpoint: "https://graph.microsoft.com/v1.0/me"
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, acquireTokenRedirectCallBack,
{ storeAuthStateInCookie: true, cacheLocation: "localStorage" });
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function signOut() {
myMSALObj.logout();
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
// accessToken
}, function (error) {
console.log(error);
});
}
I managed to find what was causing the problem.
So basically only Live Connect SDK generated tokens are valid on
https://xxx.azurewebsites.net/.auth/login/microsoftaccount
We were using MSAL which was generating tokens valid only on Azure Active Directory. I have been in touch with Azure Support, and have asked them to update the documentation. It currently is very confusing as none of these have been explained in the EasyAuth documentations.
We decided to go with Azure AD B2C, as it's more reliable and turns out cheaper for us.
In case anyone would like to use EasyAuth with Microsoft Account, the following is showing how to get access token from Live SDK
WL.Event.subscribe("auth.login", onLogin);
WL.init({
client_id: "xxxxxx",
redirect_uri: "xxxxxx",
scope: "wl.signin",
response_type: "token"
});
WL.ui({
name: "signin",
element: "signin"
});
function onLogin(session) {
if (!session.error) {
var access_token = session.session.access_token;
mobileClient.login('microsoftaccount', { 'access_token': access_token }, false)
.then(function () {
console.log('TODO - could enable/disable functionality etc')
}, function (error) {
console.log(`ERROR: ${error}`);
});
}
else {
console.log(`ERROR: ${session.error_description}`);
}
}
Reference to
< script src="//js.live.net/v5.0/wl.js">
I've almost configured my OpenId owin authentication/authorization in Azure Active Directory. My configuration is the following:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "AppServiceAuthSession"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = _authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
RedirectUri = PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
AuthorizationCodeReceived = async context =>
{
var id = new ClaimsIdentity(context.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(context.AuthenticationTicket.Identity.Claims);
var appToken = "MyToken";
id.AddClaim(new Claim("MyTokenKey", appToken));
context.AuthenticationTicket = new AuthenticationTicket
(
new ClaimsIdentity(id.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties
);
}
},
});
But I want to add one more application token (not user token) to claims list to be able to have ability to use this token in any place on my site. Also it's good point for me that I don't need to get this token from my external token provider more then one time per an authentication session.
But place, where I'm going to add my logic (AuthorizationCodeReceived as well as other methods from OpenIdConnectAuthenticationNotifications) is called only when I use my local IIS(run locally), when I try to use azure IIS, this method has not been called at all. In this case my User is authenticated anyway, but this method and the similar methods from OpenIdConnectAuthenticationNotifications(except RedirectToIdentityProvider) are not fired.
I've downloaded the git source code of Katana project and referenced this project to my instead of the official nuget packages to debug its and as I think currently, I've found the reason why it happens. The AuthorizationCodeReceived "event" method is called from OpenIdConnectAuthenticationHandler class in AuthenticateCoreAsync method. But also, the calling of this method is required that the below checking must give the true result:
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(Request.ContentType) // May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
{
//some necessary preparation to call `AuthorizationCodeReceived` event method
}
As we can see, this checking allows only POST requests and I see these POST requests when I run app in local IIS, but I cannot see these POST requests when I deploy my application in azure portal (I've debugged both of options : on local IIS and in azure portal).
As summary from the above, this is the only one difference between these runnings. (Azure IIS doesn't send POST request at all by some reason).Any other methods in Katana project (which I checked) are called in the same way.
Could anybody help with it?
PS Note, I check any changes only after clearing of browser data (cache/history and so on).
The answer is the following:
The authorization in azure portal should be configured as shown above. In case if you chose LogIn with Azure Active Directory, then app services auth takes place outside of your app, and the custom authorization is not triggered.
I am developing a Single Page Application using .NET Core V2 and am using Azure B2C Authentication.
My Startup.cs has the following:
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
public static AuthenticationBuilder AddAzureAdB2CBearer(this AuthenticationBuilder builder)
=> builder.AddAzureAdB2CBearer(_ => { });
public static AuthenticationBuilder AddAzureAdB2CBearer(this AuthenticationBuilder builder, Action<AzureAdB2COptions> configureOptions)
{
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
builder.Services.AddScoped<IClaimsTransformation, ClaimsTransformer>();
builder.AddJwtBearer();
return builder;
}
I have a Signin Endpoint which redirects to the B2C Login Page
ie.
https://login.microsoftonline.com/{mydomain}/oauth2/v2.0/authorize?p={mysigninpolicy}&client_id={3}&nonce=defaultNonce&redirect_uri={myredirecturl}&scope=openid&response_type=id_token&response_mode=form_post&prompt=login
I have created a callback endpoint of myredirecturl which checks for any error message from B2C Sign in and grabs the Bearer token.
I have set up an Azure SignIn Policy with the myredirecturl specified.
All of my controllers are then protected with [Authorize] attributes to prevent access unless signed in.
This all works fine. But I would like the following to happen:
1) If I am logged off and I enter https://mydomain/somecontroller/somemethod
2) I would like to be taken to the SignIn page (this happens now)
3) After succesful sign in I want to be redirected automatically to
https://mydomain/somecontroller/somemethod
This does not happen now, I can only be taken to the home page because there is no way I can find to pass with ReplyUrl as a query string parameter to the SignIn Endpoint and then retrieve it from the B2C Callback.
I want my redirecturl to be whatever was submitted from the browser.
If I was using standard Identity Authentication I could do:
mydomain/account/login?redirecturl=mydomain/controller/method
Found the answer:
If you include a &state={some value} parameter in the call to B2C login ie.
https://login.microsoftonline.com/{mydomain}/oauth2/v2.0/authorize?p={mysigninpolicy}&client_id={3}&nonce=defaultNonce&redirect_uri={myredirecturl}&scope=openid&response_type=id_token&response_mode=form_post&prompt=login&state=myvalue
the endpoint that B2C calls in the redirect_uri also includes this value, so you can use this to redirect the user after successful login.