OAuthOptions.Scope Cannot Be Assigned To - oauth

I'm trying to implement LinkedIn/OAuth authentication in my ASP.NET Core 2.0 app and I need to set the scope to { "r_basicprofile", "r_emailaddress" } so that I can user's email, profile image, etc.
When I try to set the scope in the following code, I'm getting the following error:
Property or indexer 'OAuthOptions.Scope' cannot be assigned to -- it's
read-only.
Here's the code:
services.AddOAuth(CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.SignInScheme = "LinkedIn";
options.ClientId = "1234567890";
options.ClientSecret = "1234567890";
options.CallbackPath = "/linkedin-callback";
// Configure the LinkedIn endpoints
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization",
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken",
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,email-address,picture-url,picture-urls::(original))",
options.Scope = { "r_basicprofile", "r_emailaddress" };
options.Events = new OAuthEvents
{
OnCreatingTicket = OnCreatingTicketLinkedInCallBack,
OnTicketReceived = OnTicketReceivedCallback
};
})
Any idea how I can set the scope?
P.S. I tried to adapt the code from my ASP.NET Core 1.1. This code was working fine in the ASP.NET Core 1.1 app.

The Scope = { "r_basicprofile", "r_emailaddress" } syntax is only available when using object initialization. Here, the options object is not instantiated from your code but directly provided by ASP.NET Core so you can't use this syntax.
The good news is that Scope property is a collection (internally, a hash set) so you can simply do:
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
If you want to get rid of the default scopes, you can remove them using options.Scope.Remove("scope") or options.Scope.Clear().

Related

itfoxtec-identity-saml2 SAML Request Destination Port being stripped out

On making a SAML request the port number (443) is being stripped out of the Destination. I understand this is default behaviour of the URI object. However the SAML identity provider requires the destination includes the port number for validation.
How can I get the SAML builder to include the port? 443 is being stripped from https://sit-api.eat.xxxxxx.xxxx.xx:443/samlsso (see below)
Saml2Configuration samlconfig = GetSAMLConfig();
var samlRequest = new Saml2AuthnRequest(samlconfig);
samlRequest.AssertionConsumerServiceUrl = new Uri(_appConfiguration["Saml2:AssertionConsumerServiceUrl"]);
samlRequest.Destination = new Uri(_appConfiguration["Saml2:SingleSignOnDestination"]); // https://sit-api.eat.xxxxxx.xxxx.xx:443/samlsso
samlRequest.NameIdPolicy = new NameIdPolicy()
{
AllowCreate = false,
Format = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
SPNameQualifier = _appConfiguration["Saml2:SPNameQualifier"]
};
samlRequest.Conditions = new Condition();
samlRequest.Conditions.Items = new List<ITfoxtec.Identity.Saml2.Schemas.Conditions.ICondition>();
samlRequest.Conditions.Items.Add(new ITfoxtec.Identity.Saml2.Schemas.Conditions.AudienceRestriction() { Audiences = new List<Audience>() { new Audience() { Uri = _appConfiguration["Saml2:AllowedAudienceUris"] } } });
var bnd = binding.Bind(samlRequest);
It is possible to change the destination URL after the ToActionResult method has been called if you are using a Saml2RedirectBinding. And thereby overriding the default behavior.
Like this:
var action = binding.ToActionResult() as RedirectResult;
action.Url = action.Url.Replace("https://sit-api.eat.xxxxxx.xxxx.xx/samlsso", "https://sit-api.eat.xxxxxx.xxxx.xx:443/samlsso");
return action;

How to use SimpleProvider with my own MSAL C# code

I'm trying to use my own MSAL code to work together. Developed with .NET Core 5 MVC.
I have similar problem as I found in below link. But I just don't know how to make it work with the proposed answer. Or in other words, I'm still confuse how this integration is done.
[It is mandatory to use the login component in order to use the other components]It is mandatory to use the login component in order to use the other components
[Quickstart for MSAL JS]https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/examples/simple-provider.html
I also have read following article too:
[Simple Provider Example]https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/examples/simple-provider.html
[A lap around microsoft graph toolkit day 7]https://developer.microsoft.com/en-us/office/blogs/a-lap-around-microsoft-graph-toolkit-day-7-microsoft-graph-toolkit-providers/
is there someone can pointing to me more details explanation about how to archive this.
Can someone explains further below response further. How to do it. Where should I place the code and how to return AccessToken to SimpleProvider?
Edited:
Update my question to be more precise to what I want besides on top of the question. Below is the code I used in Startup.cs to automatically trigger pop up screen when user using the web app. When using the sample provided, it is always cannot get access token received or userid data. Question 2: How to save or store token received in memory or cache or cookies for later use by ProxyController and its classes.
//Sign in link under _layouts.aspx
<a class="nav-link" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
// Use OpenId authentication in Startup.cs
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
// Specify this is a web app and needs auth code flow
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAd", options);
options.Prompt = "select_account";
options.Events.OnTokenValidated = async context =>
{
var tokenAcquisition = context.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(GraphConstants.Scopes, user: context.Principal);
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
})
);
// Get user information from Graph
try
{
var user = await graphClient.Me.Request()
.Select(u => new
{
u.DisplayName,
u.Mail,
u.UserPrincipalName,
u.MailboxSettings
})
.GetAsync();
context.Principal.AddUserGraphInfo(user);
}
catch (ServiceException)
{
}
// Get the user's photo
// If the user doesn't have a photo, this throws
try
{
var photo = await graphClient.Me
.Photos["48x48"]
.Content
.Request()
.GetAsync();
context.Principal.AddUserGraphPhoto(photo);
}
catch (ServiceException ex)
{
if (ex.IsMatch("ErrorItemNotFound") ||
ex.IsMatch("ConsumerPhotoIsNotSupported"))
{
context.Principal.AddUserGraphPhoto(null);
}
}
};
options.Events.OnAuthenticationFailed = context =>
{
var error = WebUtility.UrlEncode(context.Exception.Message);
context.Response
.Redirect($"/Home/ErrorWithMessage?message=Authentication+error&debug={error}");
context.HandleResponse();
return Task.FromResult(0);
};
options.Events.OnRemoteFailure = context =>
{
if (context.Failure is OpenIdConnectProtocolException)
{
var error = WebUtility.UrlEncode(context.Failure.Message);
context.Response
.Redirect($"/Home/ErrorWithMessage?message=Sign+in+error&debug={error}");
context.HandleResponse();
}
return Task.FromResult(0);
};
})
// Add ability to call web API (Graph)
// and get access tokens
.EnableTokenAcquisitionToCallDownstreamApi(options =>
{
Configuration.Bind("AzureAd", options);
}, GraphConstants.Scopes)
// Add a GraphServiceClient via dependency injection
.AddMicrosoftGraph(options =>
{
options.Scopes = string.Join(' ', GraphConstants.Scopes);
})
// Use in-memory token cache
// See https://github.com/AzureAD/microsoft-identity-web/wiki/token-cache-serialization
.AddInMemoryTokenCaches();
Since you are using MVC, I recommend using the ProxyProvider over the Simple Provider.
SimpleProvider - useful when you have existing authentication on the client side (such as Msal.js)
ProxyProvider - useful when you are authenticating on the backend and all graph calls are proxied from the client to your backend.
This .NET core MVC sample might help - it is using the ProxyProvider with the components
Finally, I have discovered how to do my last mile bridging for these two technology.
Following are the lines of the code that I have made the changes. Since I'm using new development method as oppose by MSAL.NET, a lot of implementation has been simplified, so many of examples or article out there, may not really able to use it directly.
Besides using links shared by #Nikola and me above, you also can try to use below
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/
to consolidate to become your very own solution. Below are the changes I have made to make it worked.
Change in Startup.cs class
// Add application services. services.AddSingleton<IGraphAuthProvider, GraphAuthProvider>(); //services.AddSingleton<IGraphServiceClientFactory, GraphServiceClientFactory>();
Change in ProxyController.cs class
private readonly GraphServiceClient _graphClient;
public ProxyController(IWebHostEnvironment hostingEnvironment, GraphServiceClient graphclient)
{
_env = hostingEnvironment;
//_graphServiceClientFactory = graphServiceClientFactory;
_graphClient = graphclient;
}
Change in ProcessRequestAsync method under ProxyController.cs
//var graphClient = _graphServiceClientFactory.GetAuthenticatedGraphClient((ClaimsIdentity)User.Identity);
var qs = HttpContext.Request.QueryString;
var url = $"{GetBaseUrlWithoutVersion(_graphClient)}/{all}{qs.ToUriComponent()}";
var request = new BaseRequest(url, _graphClient, null)
{
Method = method,
ContentType = HttpContext.Request.ContentType,
};

How to get token well retrieved in services into controller (Core 2.0 oAuth 2)?

In ASP.Net MVC Core 2, we are trying to call the Linkedin web API with OAuth authentication.
We are able to declare the OAuth authentication service and retrieve the access token from Linkedin as shown in the code below.
Now we would like to request the API from a controller. To do that, we have to get the access token from the OAuth service we have declared with the AddOAuth method. How can we do that? No way to find an example anywhere.
Thanx for your help, we are really stuck.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie("linkedin_login", options =>
{
options.LoginPath = new PathString("/login");
options.LogoutPath = new PathString("/logout");
})
.AddOAuth("LinkedIn", options =>
{
options.SignInScheme = "linkedin_login";
options.ClientId = Configuration["linkedin:clientId"];
options.ClientSecret = Configuration["linkedin:clientSecret"];
options.CallbackPath = new PathString("/signin-linkedin");
options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
options.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,first-name,last-name,formatted-name,email-address,picture-url,picture-urls,headline,public-profile-url,industry,three-current-positions,three-past-positions,positions::(original))";
// To save the tokens to the Authentication Properties we need to set this to true
// See code in OnTicketReceived event below to extract the tokens and save them as Claims
options.SaveTokens = true;
options.Scope.Add("r_basicprofile");
options.Scope.Add("r_emailaddress");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
#region OnCreatingTicket
// Retrieve user info
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
//We have here the token: context.AccessToken
request.Headers.Add("x-li-format", "json"); // Tell LinkedIn we want the result in JSON, otherwise it will return XML
the solution is just :
var AccessToken = await HttpContext.GetTokenAsync("LinkedIn", "access_token");
with "LinkedIn" the scheme of the desired oAuth

Rally Authentication issue in Getting results

I am using asp.net MVC application and consuming Rally web API for integration. I want fetch the data from rally site.
in Login Controller
RallyRestApi restApi = new RallyRestApi(webServiceVersion: "v2.0");
dynamic authenticateUser=restApi.Authenticate(usr.UserName, usr.Password, "https://rally1.rallydev.com/", allowSSO: false);
dynamic objUserName;
if (authenticateUser.ToString().ToLower() == "authenticated")
{
Session["Username"] = usr.UserName;
Session["Password"] = usr.Password;
FormsAuthentication.SetAuthCookie(usr.UserName, true);
FormsAuthentication.SetAuthCookie(usr.Password, true);
objUserName = restApi.GetCurrentUser();
Session["DisplayName"] = objUserName["DisplayName"];
return RedirectToAction("Home", "PortfolioItem");
}
Here Authentication is successful. But as per my research, if we want to fetch data every time, I think we need to pass user authentication details like below:
CreateResult createResult = restApi.Create("defect", toCreate); // need to get with same restApi object or authentication details
OperationResult updateResult = restApi.Update(createResult.Reference, toUpdate);
//Get the item
DynamicJsonObject item = restApi.GetByReference(createResult.Reference);// need to get with same restApi object or authentication details
//Query for items
Request request = new Request("defect");
request.Fetch = new List<string>() { "Name", "Description", "FormattedID" };
request.Query = new Query("Name", Query.Operator.Equals, "My Defect");
QueryResult queryResult = restApi.Query(request); // need to get with same restApi object or authentication details
Like above, is it if we need to fetch anything, we need to authenticate first and every time? please clarify on this.
You'll need to authenticate once for each instance of RallyRestApi you create. In general it is better to create one, use it, and then dispose of it rather than creating it once and then keeping it around in session forever.

Asp.net mvc 5; Where to add custom claims? (adfs login)

We have a mvc 5 project set up with adfs (ws-federation) authentication. Now, we want to add a custom claim to the user if a flag is true in the db. Where would be the correct place to do this?
This blog entry got us in the right direction. The claims need to be injected at the last minute after the user is validated.
Startup.Auth.cs should look something like this:
app.UseActiveDirectoryFederationServicesBearerAuthentication(
new ActiveDirectoryFederationServicesBearerAuthenticationOptions
{
MetadataEndpoint = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
//NameClaimType = "User-Principal-Name",
//SaveSigninToken = true
},
//Inject custom claims from Database
Provider = new OAuthBearerAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
string UPN = context.Ticket.Identity.FindFirst(ClaimTypes.Upn).Value;
UPN = UPN.Remove(UPN.Length - 12);
User user = new User();
//user = GetUserData("user#domain.com");
user = GetUserData(UPN); //Get user data from your DB
context.Ticket.Identity.AddClaim(
new Claim("UserName", user.UserName.ToString(), ClaimValueTypes.String, "LOCAL AUTHORITY"));
}
}
});
The easiest way is to set up a SQL attribute store and then write a custom rule to query the store.
As per the article, something like:
c:[type == "http://contoso.com/emailaddress"]
=> issue (store = "Custom SQL Store", types = ("http://contoso.com/age", "http://contoso.com/purchasinglimit"), query = "SELECT age,purchasinglimit FROM users WHERE email={0}",param = c.value);

Resources