openid - splitting up request for two endpoints (c#) - c#

how do you use identitymodel for c# to get a authorize code?
i need to make two seperate calls because the server infrastructure, one /auth and one /token, i have to work with apparently does not work with just one endpoint like google can do.
theres this: authorize url
but that only creates the request, im missing something like this for the authorize endpoint:
var client = new HttpClient();
var response = await client.RequestTokenAsync(new TokenRequest
{
Address = "https://demo.identityserver.io/connect/token",
GrantType = "custom",
ClientId = "client",
ClientSecret = "secret",
Parameters =
{
{ "custom_parameter", "custom value"},
{ "scope", "api1" }
}
});
i did choose this library because it is shown here as certified.

You first have to get the authorization code (using the authorization code flow) and you need to redirect the users browser to the ru variable as it is described here.
In theory, just a link pointing to that variable, like
Login to IdentityServer
In the response back you should eventually get back the authorization code. From that code you can then use IdentityModel to get the final tokens.

Related

JWT auth with asp.net core to create token and store in http only cookies and angular to call method with header

I am new to JWT with basic idea of how it works. I have set the jwt token inside cookie from my web api.
Response.Cookies.Append("X-Access-Token", foundUser.Token
, new CookieOptions { HttpOnly = true }) ;
Now i am trying to call a web api get request which is marked as authorised from my agular application.
But inside angular i dont have a way to send the cookie. As per few documents i came to know that http only cookies are sent directly with our interference but i am not able to fetch the data with unauthorised error, which means that the token is not being used. I have not included the same in the get method as well. see below.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
public async Task<ActionResult<IEnumerable<Invoice>>> GetInvoices([FromQuery]QueryParameters queryParameters)
{
return await _uOW.InvoiceRepository.GetInvoices(queryParameters);
}
Do i have to add some logic to read the cookie in backend? I didnt find any answer for this.
Please let me know if there is something missing.
Also inside angular i have not written any special code for calling this get. Is that required?
var headerForToken = new HttpHeaders({'Authorization':`Bearer ${sessionStorage.getItem("token")}`});
return this.http.get<any>(this.APIUrl+'Invoices/GetInvoices',{headers:headerForToken });
This is my current approach in which i am using local storage, but i really need to use the http only cookie.
If there is any link for solution or anything that would be really helpfull
Update 1: So we need to add a domain for this. I tried adding domain still it is not visible when i try to see the cookies.
Response.Cookies.Append("X-Access-Token", foundUser.Token
, new CookieOptions { HttpOnly = true, Domain = ".localhost:4200" }) ;

Unable to sign in to login.microsoftonline.com oauth 2 authorize endpoint

I've tried different ways to connect the Microsoft sign in function which open a webpage so you can use things like sign in with MFA. I manage to get this to work in Postman and now im trying it in C# particularly in .NET MVC 5.
HomeController:
public ActionResult TestAuth()
{
HttpClient client = new HttpClient();
var bodyParams = new Dictionary<string, string>();
bodyParams.Add("client_id", "{my_client_id}");
bodyParams.Add("client_secret", "{my_client_secret}");
bodyParams.Add("scope", "openid");
bodyParams.Add("redirect_uri", "https://localhost");
bodyParams.Add("grant_type", "authorization_code");
var response = client.PostAsync("https://login.microsoftonline.com/{my_tenant_id}/oauth2/v2.0/authorize", new FormUrlEncodedContent(bodyParams)).Result;
return View("TestAuth", new { response.Content.ReadAsStringAsync().Result });
}
View TestAuth.cshtml:
#model dynamic
#Html.Raw(Model)
If i sign in with my email on that domain, or any text at all really, i get this message. I cannot see why this issue occurs it gives me zero information what to do next more than just trying until you make it basically :). I've looked at tons of different Microsoft documentations, Stack posts, forums etc but with no success.
The postman call example:
Is it possible I'm doing something wrong in the request in the c# code or am i missing something important like configurations in Azure AD etc?
I'm up for anything that will work that i can sign into a Microsoft account that use MFA, then i can use their login to fetch data from Microsoft Graph based on their permissions basically.
P.S. I also can fetch data with the access token generated from postman so it's working as expected. I only need to "convert the postman call to c#" to make it work esentially. Any help is appreciated :)
You’re trying to do an oauth2 request from the controller. The request you’re sending is incorrect.
Microsoft made a great sample on how to use the Microsoft identity platform in a dotnet application https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC
In a nutshell you redirect the user to the endpoint (so not a get/post from the controller, but actually a redirect 302 response to the token url).
The user then has to login and is redirected to the webapplication.
Your webapplication will get an authorization code that is has to exchange for an access token by a post request.
Postman does this for you, but in order to do it in dotnet core, just follow the sample.
I didn't find a soultion to this specific problem what i did find was another guide which led me to this github project https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
Which had similar code in the Startup.cs file but actually had some examples like SendMail and ReadMail etc which was fetched from ms graph api. This gave me some idea of how this project was structured compared to mine. So one thing that was missing was this part I couldnt figure out:
IConfidentialClientApplication app = await MsalAppBuilder.BuildConfidentialClientApplication();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
So the Msal app builder which is a custom made thingy i needed to get the current user etc which i needed. This works fine and after that i can start doing requests to the graph api like adding scopes etc and make http request.
Example see groups:
[Authorize]
[HttpGet]
public async Task<ActionResult> GetGroups()
{
IConfidentialClientApplication app = await MsalAppBuilder.BuildConfidentialClientApplication();
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetAccountId());
string[] scopes = { "GroupMember.Read.All", "Group.Read.All", "Group.ReadWrite.All", "Directory.Read.All", "Directory.AccessAsUser.All", "Directory.ReadWrite.All" };
AuthenticationResult result = null;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/groups");
try
{
//Get acccess token before sending request
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
if (result != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
//Request to get groups
HttpResponseMessage response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
ViewBag.Groups= response.Content.ReadAsStringAsync().Result;
return View("MyView");
}
}
}
catch (Exception ex)
{
Response.Write($"Error occured:{System.Environment.NewLine}{ex}");
}
return View();
}

OAuth with custom JWT authentication

I'm struggling to implement a custom auth flow with OAuth and JWT.
Basically it should go as follows:
User clicks in login
User is redirect to 3rd party OAuth login page
User logins into the page
I get the access_token and request for User Info
I get the user info and create my own JWT Token to be sent back and forth
I have been following this great tutorial on how to build an OAuth authentication, the only part that differs is that Jerrie is using Cookies.
What I Have done so far:
Configured the AuthenticationService
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "3rdPartyOAuth";
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie() // Added only because of the DefaultSignInScheme
.AddJwtBearer(options =>
{
options.TokenValidationParameters = // Ommited for brevity
})
.AddOAuth("3rdPartyOAuth", options =>
{
options.ClientId = securityConfig.ClientId;
options.ClientSecret = securityConfig.ClientSecret;
options.CallbackPath = new PathString("/auth/oauthCallback");
options.AuthorizationEndpoint = securityConfig.AuthorizationEndpoint;
options.TokenEndpoint = securityConfig.TokenEndpoint;
options.UserInformationEndpoint = securityConfig.UserInfoEndpoint;
// Only this for testing for now
options.ClaimActions.MapJsonKey("sub", "sub");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Request for user information
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
};
});
Auth Controller
[AllowAnonymous]
[HttpGet("login")]
public IActionResult LoginIam(string returnUrl = "/auth/loginCallback")
{
return Challenge(new AuthenticationProperties() {RedirectUri = returnUrl});
}
[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
// Here is where I expect to get the user info, create my JWT and send it back to the client
return Ok();
}
Disclaimer: This OAuth flow is being incorporated now. I have a flow for creating and using my own JWT working and everything. I will not post here because my problem is before that.
What I want
In Jerrie's post you can see that he sets DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;. With that, when the /auth/loginCallback is reached I have the user claims in the HttpContext.
The problem is my DefaultAuthenticateScheme is set to JwtBearersDefault and when the loginCallback is called I can't see the user claims nowhere in the Request.
How can I have access to the information gained on the OnCreatingTicketEvent in my callback in this scenario?
Bonus question: I don't know much about OAuth (sure that is clear now). You may have noted that my options.CallbackPath differs from the RedirectUri passed in the Challenge at the login endpoint. I expected the option.CallbackPath to be called by the 3rd Part OAuth provider but this is not what happens (apparently). I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right? The Callback is used for nothing but a match configuration? I can even comment the endpoint CallbackPath points to and it keep working the same way...
Thanks!
Auth
As Jerrie linked in his post, there is a great explanation about auth middlewares:
https://digitalmccullough.com/posts/aspnetcore-auth-system-demystified.html
You can see a flowchart in the section Authentication and Authorization Flow
The second step is Authentication middleware calls Default Handler's Authenticate.
As your default auth handler is Jwt, the context is not pupulated with the user data after the oauth flow,
since it uses the CookieAuthenticationDefaults.AuthenticationScheme
Try:
[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
//
// Read external identity from the temporary cookie
//
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("Nein");
}
var oauthUser = result.Principal;
...
return Ok();
}
Great schemes summary: ASP.NET Core 2 AuthenticationSchemes
You can persist your user with
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhttpcontextextensions.signinasync?view=aspnetcore-2.2
Bonus
I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right?"
Yes.
For security reasons, the registered callback uri (on authorization server) and the provided callback uri (sent by the client) MUST match.
So you cannot change it randomly, or if you change it, you have to change it on the auth server too.
If this restriction was not present, f.e. an email with a mailformed link (with modified callback url) could obtain grant.
This is called Open Redirect, the rfc refers to it too: https://www.rfc-editor.org/rfc/rfc6749#section-10.15
OWASP has a great description: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md
I can even comment the endpoint CallbackPath points to and it keep working the same way..."
That is because your client is trusted (you provide your secret, and you are not a fully-frontend Single Page App). So it is optional for you to send the callback uri.
But IF you send it, it MUST match with the one registered on the server. If you don't send it, the auth server will redirect to the url, that is registered on its side.
https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
redirect_uri
OPTIONAL. As described in Section 3.1.2.
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2
The authorization server redirects the user-agent to the
client's redirection endpoint previously established with the
authorization server during the client registration process or when
making the authorization request.
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.2
The authorization server MUST require the following clients to register their redirection endpoint:
Public clients.
Confidential clients utilizing the implicit grant type.
Your client is confidential and uses authorization code grant type (https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.3
If multiple redirection URIs have been registered, if only part of
the redirection URI has been registered, or if no redirection URI has
been registered, the client MUST include a redirection URI with the
authorization request using the "redirect_uri" request parameter.
You have registered your redirect uri, that's why the auth server does not raise an error.
change [AllowAnonymous]
to [Authorize]
on the 'loginCallback' endpoint (AuthController.IamCallback method)

Redirect to URL instead of 401 for unauthenticated

I am using ASP.Net 5 MVC 6 with JWT tokens that are created while the user is on another site which this site is a subdomain of. My goal is to pass the token along with the request to this subdomain. If a user happens to try to come to this subdomain url without the proper token in the header then I want to redirect them to the main site login page.
After much frustration with the newest RC-1 release and using JWT tokens with a SecureKey instead of certificates. I finally got my code working by using the RC-2 nightly build version. Now my problem is that I want to be able to redirect to an outside url in the case of unauthenticated users. Here is an example of my authentication code:
var key = "mysupersecretkey=";
var encodedkey2 = Convert.FromBase64String(key);
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(encodedkey2);
options.TokenValidationParameters.ValidIssuer = "https://tv.alsdkfalsdkf.com/xxx/yyy";
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.ValidAudience = "https://www.sdgfllfsdkgh.com/";
options.TokenValidationParameters.ValidateAudience = true;
options.Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration()
{
Issuer = "https://tv.sdfkaysdf.com/xxx/yyy"
};
});
now I see other examples which are using OpedId and they have it pretty easy , there is a parameter called RedirectUrl
app.UseOpenIdConnectAuthentication(options => {
...
options.RedirectUri = "https://localhost:44300";
...
});
any idea how to set RedirectUrl when using JwtBearerAuthentication ???
There's no such property for a simple reason: the JWT bearer middleware (like the more generic OAuth2 middleware in Katana) has been designed for API authentication, not for interactive authentication. Trying to trigger a redirection in this case wouldn't make much sense for headless HTTP clients.
That said, it doesn't mean that you can't redirect your unauthenticated users at all, at some point. The best way to handle that is to catch the 401 response returned by the JWT middleware at the client level and redirect the user to the appropriate login page. In JS applications for instance, this is usually done using an HTTP interceptor.
If you're really convinced breaking the OAuth2 bearer specification is the right thing to do, you can do that using the OnChallenge notification:
app.UseJwtBearerAuthentication(options => {
options.Events = new JwtBearerEvents {
OnChallenge = context => {
context.Response.Redirect("http://localhost:54540/login");
context.HandleResponse();
return Task.FromResult(0);
}
};
});

How to call GData services with an OAuth access token?

I have an OAuth access token that I got from:
var state = new AuthorizationState(new[] { "http://www.google.com/m8/feeds/" })
{
Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl)
};
// Redirect to:
var authUri = arg.RequestUserAuthorization(state)
...
var authState = arg.ProcessUserAuthorization(authCode, state);
After that, how do I pass the authState.AccessToken to the services from the GData API?
All the examples I find pass an OAuth2Authenticator<> to the constructor of the service, such as:
var auth = new OAuth2Authenticator<NativeApplicationClient>(...);
var service = new PlusService(auth);
I am trying to use the ContactsService but the latest version (2.2.0.0) does not seem to have this constructor - the only constructor takes an application name.
I have tried setting the access token with ContactsService.SetAuthenticationToken() but the request header is incorrectly set, it will look like this:
Authorization: GoogleLogin auth=<access token>
Instead of:
Authorization: Bearer <access token>
There does not seem to be any method to manipulate the headers either.
We ran into the same problem. Instead of using SetAuthenticationToken(), you should assign the RequestFactory with an OAuth2RequestFactory as suggested by the examples provided on the GData API page.
Now this seems somewhat counter-intuitive if you already are managing OAuth tokens outside of the GData API. It turns out that you do not need to actually provide the clientId, clientSecret, etc to the request factory. The only field that matters is the OAuth access token, if that is a valid access token.
At least for us, we found that with this approach, the GData API correctly specifies the Authorization Http header, and the API calls are successful.

Categories

Resources