I have one Master application(MainApp) and one Client Application(clientApp). I am trying to login ClientApp through MainApp.(Having a Login button in ClientApp and it will go to MainApp for login). I did setup in Startup.cs file as below in ClientApp:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", "OneIAM", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:5001";
options.ClientId = "XXXXXX858";
options.ClientSecret = "XXXXXXXXXXXXXXXXHDDH6os6/xJr7qGmmSJQxViHiq585KdghDaLnQ1Rb03J==";
options.CallbackPath = "/Index";
options.SignedOutRedirectUri = "https://localhost:5001/signout-callback-oidc";
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.ResponseType = "code";
options.SaveTokens = true;
});
In above code, "https://localhost:5001" is the URL for MainApp.
I have register ClientId and ClientSecret in MainApp using below code :
dotnet user-secrets set 'HttpClient:CbApi:ClientId' 'XXXX858'
dotnet user-secrets set 'HttpClient:CbApi:ClientSecret' 'XXXXXXXXXXXXXXXXHDDH6os6/xJr7qGmmSJQxViHiq585KdghDaLnQ1Rb03J=='
Now, after running both(MainApp and ClientApp), I have Login button on ClientApp and after clicking on Login button it's redirecting to MainApp login page. But, after providing credential and hitting login button, it's showing error as below
An unhandled exception occurred while processing the request.
OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'error_description is null', error_uri: 'error_uri is null'.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler.RedeemAuthorizationCodeAsync(OpenIdConnectMessage tokenEndpointRequest)
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
FYI : i have maintained all the client info in database table as well
Please help me in this and let me know if you need anything else.
Related
I have a .NET Core app which uses identityserver4 to authenticate users. I have integrated it with ASP.NET Identity (Microsoft.AspNetCore.Identity.UI) and this works fine. It uses the AspNetUser tables etc. to store users. etc etc and all the options work.
I would like to add the option to use Azure Active Directory users. So I add the following code to my startup class (previously there was just services.AddAuthentication();):
services.AddAuthentication()
.AddOpenIdConnect("aad", "Azure AD", options =>
{
options.Authority = "https://login.windows.net/<My Azure Tenant Guid>";
options.TokenValidationParameters =
new TokenValidationParameters { ValidateIssuer = true };
options.ClientId = "<My Azure App Client Id>";
options.CallbackPath = "/signin-aad";
options.SignedOutCallbackPath = "/signout-callback-aad";
options.RemoteSignOutPath = "/signout-aad";
options.ResponseType = OpenIdConnectResponseType.Code;
options.ClientSecret = "<My Azure App Client Secret>";
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.RequireHttpsMetadata = true;
})
;
This makes a button available to add your Azure AD account... Which doesn't work - it gets as far as asking for permission, then comes up with "Unexpected error occurred loading external login info".
Any ideas, or does anyone have a link to a good tutorial?
I use two discord provider with different clientid for login.The code below:
services.AddAuthentication()
.AddDiscord("ADiscord", u =>
{
u.ClientId = "74627xxx8536";
u.ClientSecret = "B-FLxxxxjp3JOKwr27";
u.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
u.Scope.Add("guilds.join");
u.SaveTokens = true;
u.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
u.CorrelationCookie.IsEssential = true;
})
.AddDiscord("BDiscord", u =>
{
u.ClientId = "71475xxx1925";
u.ClientSecret = "45-xxxA4okXx1I";
u.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
u.Scope.Add("guilds.join");
u.SaveTokens = true;
u.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
u.CorrelationCookie.IsEssential = true;
});
When i login with scheme "ADiscord" everything is ok.But use scheme "BDiscord" it throw an error "Error from RemoteAuthentication: The oauth state was missing or invalid".
What's even more strange is that if change the order let "BDiscord" on the top then login with scheme "BDiscord" everything is ok,"ADiscord" will throw the error.
Disord provider code is on https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers/tree/dev/src/AspNet.Security.OAuth.Discord
You need to set the CallbackPath, set it to sth like /signin-discord and register /signin-discord with the IdSvr4. This endpoint is used to complete the OAuth handshake before redirecting to an application endpoint.
I'm using .NET Core 3.1 with Identity Server 4 and connecting to Azure AD via OpenIdConnect. I'm using a Vue.js front-end and .NET Core API. IdentityServer, the front-end, and the API are all hosted on-prem on the same server (same domain). Everything uses https. I'm using an Oracle database with EF model first, with fully-customized IdentityServer stores and a custom user store (I implemented the interfaces). I'm using IdentityServer's Quickstart, edited a little to hook up my custom user store instead of the test user. I'm running this in my dev environment.
If I type in the url to the IdentityServer, I'm redirected to Azure AD, signed-in successfully, and shown this page:
Grants - successful login
The claims are coming back from Azure AD and the auto-provisioning is successful. It is written successfully to the database.
Authenticating through my JS client hits IdentityServer, redirects to Azure AD, I sign-in, then it redirects to IdentityServer's ExternalController, then redirects back to a Microsoft url, then proceeds to repeat until it finally fails with this page:
Sign-in failure from Azure AD
My guess is I messed up a redirect uri somewhere. Here is my code and the IdentityServer log:
IdentityServer Log
That block of logging repeats 6-10 times. No errors or anything different at the end.
I had to break up the C# code because the site couldn't handle one of my long options lines.
IdentityServer Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10),
CookieSlidingExpiration = true
};
}).AddClientStore<ClientStore>()
.AddCorsPolicyService<CorsPolicyService>()
.AddResourceStore<ResourceStore>()
.AddPersistedGrantStore<PersistedGrantStore>()
.AddProfileService<UserProfileService>();
services.AddScoped<IUserStore, UserStore>();
if (env.IsDevelopment())
{
// not recommended for production
builder.AddDeveloperSigningCredential();
}
else
{
// TODO: Load Signing Credentials for Production.
}
services.AddAuthentication()
.AddOpenIdConnect("aad", "Azure AD", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://login.windows.net/[authority]";
options.CallbackPath = "/callback-aad";
options.ClientId = "[ClientId]";
options.RemoteSignOutPath = "/signout-aad";
options.RequireHttpsMetadata = true;
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.SaveTokens = true;
options.SignedOutCallbackPath = "/signout-callback-aad";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
options.UsePkce = true;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseStaticFiles();
app.UseSerilogRequestLogging();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Client OIDC config:
const oidcSettings = {
authority: '[IdentityServerUrl]',
client_id: '[ClientId]',
post_logout_redirect_uri: '[front-end url]/logout-aad',
redirect_uri: '[front-end url]/callback-aad',
response_type: 'code',
save_tokens: true,
scope: 'openid profile',
}
Callback method being hit for ExternalController:
[HttpGet]
public async Task<IActionResult> Callback()
{
// read external identity from the temporary cookie
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}
if (_logger.IsEnabled(LogLevel.Debug))
{
var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}");
_logger.LogDebug("External claims: {#claims}", externalClaims);
}
// lookup our user and external provider info
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result);
if (user == null)
{
// this might be where you might initiate a custom workflow for user registration
// in this sample we don't show how that would be done, as our sample implementation
// simply auto-provisions new external user
user = await AutoProvisionUser(provider, providerUserId, claims);
}
// this allows us to collect any additional claims or properties
// for the specific protocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallback(result, additionalLocalClaims, localSignInProps);
// issue authentication cookie for user
var isuser = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username,
IdentityProvider = provider,
AdditionalClaims = additionalLocalClaims
};
await HttpContext.SignInAsync(isuser, localSignInProps);
// delete temporary cookie used during external authentication
await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
// retrieve return URL
var returnUrl = result.Properties.Items["returnUrl"] ?? "~/";
// check if external login is in the context of an OIDC request
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId));
if (context != null)
{
if (context.IsNativeClient())
{
// The client is native, so this change in how to
// return the response is for better UX for the end user.
return this.LoadingPage("Redirect", returnUrl);
}
}
return Redirect(returnUrl);
}
Azure AD config:
redirect uri: [IdentityServer url]/callback-aad
Database table data:
Client table IMG1
Client table IMG2
ClientScopes table
ClientRedirectUris table
Please let me know if you need any additional information. Thank you
The problem was in my custom UserStore. I was getting the user by the Azure AD SubjectId instead of the UserSubjectId. So in the ExternalController, the ApplicationUser object was coming up as null. Instead of an exception, it kept going back to Azure AD to try to get the user again, but obviously that just creates an infinite loop. I didn't think to look there since my user was successfully provisioned with Id's and claims.
I have a new IdP that implements IdentityServer4 (.NET Core). I am using it to provide SSO/Cookie authentication/authorization to an MVC5 client app. Since the client app is not .NET Core, I use the IdentityServer3 and Microsoft.Owin nugets in order to integrate. There aren't tons of examples of mixing .NET Core and .NET together like this, but there are a few and I've done my best to make it work. Here is the configuration source code for each:
IdentityServer4 (.NET Core, based largely on this example):
new Client()
{
ClientId = "myClientId",
ClientName = "My Client",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
Enabled = true,
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
}
}
My Client (MVC5):
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "http://localhost:5000",
ClientId = "myClientId",
RedirectUri = "http://localhost:5002/signin-oidc",
PostLogoutRedirectUri = "http://localhost:5002/signout-callback-oidc",
ResponseType = "code id_token",
SignInAsAuthenticationType = "Cookies",
Scope = "openid",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = n => {
n.OwinContext.Response.Cookies.Append("stored_id_token", n.ProtocolMessage.IdToken);
return Task.FromResult(0);
},
RedirectToIdentityProvider = n => {
if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.Request.Cookies["stored_id_token"];
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint;
var signOutMessageId = n.OwinContext.Environment.GetSignOutMessageId(); // returns NULL!
//var signOutMessageId = n.OwinContext.Request.Query.Get("id"); // same thing as line above
if (signOutMessageId != null)
{
n.ProtocolMessage.State = signOutMessageId;
}
}
}
return Task.FromResult(0);
}
}
});
Logout mostly works ok. The issue I have is with redirecting back to the login page so that the enduser can login again to the client they just logged out of via the link highlighted here:
The RedirectUri is configured correctly on both sides of configuration (IdP and Client), and the hyperlink's href value is set in the View (pictured), but that URL also needs a query param called "state" attached to it so that it knows which application to log back into (eg. http://localhost:5002/signout-callback-oidc?state=123456789 Clicking this link without the "state" param gives you a 404.). The issue I'm having is that I'm unable to get/set this "state" value in "My Client".
From everything I've read, you get it by calling n.OwinContext.Environment.GetSignOutMessageId() which is actually just calling n.OwinContext.Request.Query.Get("id") under the covers, but it always returns null. Any idea why this returns null??
Thank you!
I'm struggling to get my simple Facebook login to work. I'm simply trying to follow the example on the website but I run into this error:
Uncaught: L {code: "auth/argument-error", message: "signInWithPopup failed: First argument "authProvider" must be a valid Auth provider."}
And here is what I'm trying to do:
export const createUserWithFacebook = () => {
let provider = firebase.auth.FacebookAuthProvider();
firebaseApp.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// firebaseApp.auth().signInWithRedirect(provider);
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
});
}
Can anyone point out what I'm doing wrong here? Thanks for your help!
At your firebase instance of the Facebook provider object should be:
let provider = new firebase.auth.FacebookAuthProvider();
this new will use firebase own constructor for the new instance.
instead of :
let provider = firebase.auth.FacebookAuthProvider();
here the link further details