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

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

Related

Creating and validating JWT token in asp net core MVC application (which does not have client server approach)

I have seen many examples to generate and validate JWT token in WEP API.
WEP API will have client and server approach. Hence we will validate user and generate JWT token in server and send to client. Client will store the token in browser memory next time through httpclient token will attached in request header and send it again to server. Now server will validate those token before hitting controller and allow to access those resource.
But in MVC application we don't have client and server approach. it will send view pages as result to browser.
My question is in MVC controller I have validated the user and created JWT token,
Now how to store the token in client
How to attach the token in request header.
Where should I do the token validation logic in MVC.
Where should I do the refresh token logic in MVC.
Thanks in Advance
According to your question, here are several solutions.
Store token in cookie, this is the recommended practice. Example:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(config=>
{
config.Cookie.Name = "auth";
})
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
//...
};
});
Although the backend can serialize the token and send it to the view (using JavaScript to store the token in Localstorage), this is not safe. Cookie can avoid csrf attacks. It is suitable for single page application.
If you put it in LocalStorage or SessionStorage, you need to get the token first and put it in the header of the request (take ajax as an exmple). Otherwise, no other configuration is required.
beforeSend: function(request) {
request.setRequestHeader("Authorization", sessionStorage.getItem("Authorization"));
}
You need to add [Authorize] to some actions, it will trigger this authentication which you configed in service. Or you can add a action to parse the tocken to get the context.
When someone update own information, you can regenerate a tocken and then send it to view to update the LocalStorage or cookie. It will carry this token in the next request.
The view can send a request to authenticate.
public IActionResult Authenticate()
{
//...
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
Response.Cookies.Append("authname", tokenString);
return View("index");
}
In startup (ConfigureServices), you can config the getting method with cookie.
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(config=>
{
config.Cookie.Name = "authname";
})
.AddJwtBearer(o =>
{
o.Events = new JwtBearerEvents()
{
//get cookie value
OnMessageReceived = context =>
{
var a = "";
context.Request.Cookies.TryGetValue("authname", out a);
context.Token = a;
return Task.CompletedTask;
}
};
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
ValidIssuer = "http://localhost:5200",
ValidAudience = "api",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("this is a long key------------------------"))
//...
};
});

using Microsoft graph API i want user profile photo

Hi I am trying to get user photo, used
var tenantId = configuration.GetSection("AzureAd").GetSection("TenantId").Value;
var clientId = configuration.GetSection("AzureAd").GetSection("ClientId").Value;
var clientSecret = configuration.GetSection("AzureAd").GetSection("clientSecret").Value;
var InviteRedirectUrl = configuration.GetSection("AzureAd").GetSection("InviteRedirectUrl").Value;
var Instance = configuration.GetSection("AzureAd").GetSection("Instance").Value;
var URL = Instance + tenantId + "/v2.0";
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority(URL)
.WithClientSecret(clientSecret)
.Build();
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
Stream photo = await graphServiceClient.Me.Photo.Content.Request().GetAsync();
I got following error
Code: BadRequest Message: Current authenticated context is not valid for this request. This occurs when a request is made to an endpoint that requires user sign-in. For example, /me requires a signed-in user. Acquire a token on behalf of a user to make requests to these endpoints. Use the OAuth 2.0 authorization code flow for mobile and native apps and the OAuth 2.0 implicit flow for single-page web apps. Inner error:
how to solve it?

Mapping a custom JSON into ClaimActions

I am developing a Angular based website in which a user is required to login using a custom OAuth2 third party authentication provider. .Net core web API is the backend. The response received from the user end point is in JSON and it is having the following format:
{
"dataSources": {
"profile": {
"username": "xyz"
}
},
"profile": {
"id": "87dfkajdfd998df"
},
"errors": {}
}
The code I am currently using is as follows:
builder.AddOAuth(oauth2Configuration.Issuer,
options => {
options.ClientId = oauth2Configuration.ClientId;
options.ClientSecret = oauth2Configuration.ClientSecret;
options.Scope.Add(oauth2Configuration.Scope);
options.ClaimsIssuer = oauth2Configuration.Issuer;
options.CallbackPath = new PathString(oauth2Configuration.ResponseType);
options.AuthorizationEndpoint = oauth2Configuration.Authority;
options.TokenEndpoint = oauth2Configuration.EndSessionEndpoint;
options.UserInformationEndpoint = oauth2Configuration.UserInfoEndpoint;
options.SaveTokens = true;
// Below mapping does not seem to work
options.ClaimActions.MapJsonSubKey(ClaimTypes.Name, "dataSources", "profile.username");
options.ClaimActions.MapJsonKey(ClaimTypes.SerialNumber, "profile.id");
// Remaining code
})
After authenticating with the above code, the claims list is always empty.
Has anyone encountered a similar situation in which claim mapping was done for custom JSON data?
That seems the OAuth authentication handler itself won't help call the endpoint , you need to manually make a call to obtain use's profile from UserInfo endpoint in OnCreatingTicket event :
OnCreatingTicket = async context =>
{
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);
}
And make claim mapping manually based on your scenario - parse/read the json using JSON.NET and add to user's princple . Please refer to below articles for code samples :
https://www.jerriepelser.com/blog/authenticate-oauth-aspnet-core-2/
https://stackoverflow.com/a/46064936/5751404

Store authentification data in MVC

I have created a custom Authorize attribute where I use the Office Graph to get AAD groups the current user is member of, and based on those I reject or authorize the user. I want to save the groups, because the call to Office Graph takes some performance. What would be the correct way to save that kind of data? I can see some people saves it to a SQL server, but then I would need to ensure cleanup etc.
Also I can see in some threads the session state is stated to be a bad choice due to concurrency. So the question is what options do you have to store this kind of information?
All suggestions are welcome.
If you were only using the group_id info, there is no need to use Office Graph and store it at all. We can enable Azure AD issue the groups claims by change the manifest of Azure AD like below:(refer this code sample)
"groupMembershipClaims": "All",
And if you are also using other info about groups, you can store these info into claims. Here is a code sample that add the name of groups into claims for your reference:
AuthorizationCodeReceived = async context =>
{
ClientCredential credential = new ClientCredential(ConfigHelper.ClientId, ConfigHelper.AppKey);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst(Globals.ObjectIdClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(ConfigHelper.Authority, new TokenDbCache(userObjectId));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.Code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigHelper.GraphResourceId);
ActiveDirectoryClient graphClient = new ActiveDirectoryClient(new Uri(ConfigHelper.GraphServiceRoot),
async () => { return await Task.FromResult(result.AccessToken); }
);
try
{
foreach (var groupClaim in context.AuthenticationTicket.Identity.FindAll("groups"))
{
var request = new HttpRequestMessage()
{
RequestUri = new Uri($"https://graph.windows.net/adfei.onmicrosoft.com/groups/{groupClaim.Value}?api-version=1.6"),
Method = HttpMethod.Get,
};
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
using (HttpClient httpClient = new HttpClient())
{
HttpResponseMessage httpResponse = httpClient.SendAsync(request).Result;
var retJSON = httpResponse.Content.ReadAsStringAsync().Result;
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(retJSON);
((ClaimsIdentity)context.AuthenticationTicket.Identity).AddClaim(new Claim("groupName", dict["displayName"].ToString()));
}
}
}
catch (Exception ex)
{
}
},
Then we can these info from controller using the code below:
ClaimsPrincipal.Current.FindAll("groupName")

MVC 5 application - implement OAuth Authorization code flow

Based on this tutorial http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server, I have created an Authorization Server, a Resource Server and a MVC Client.
The MVC Client has a Controller which gets some data from the Resource Server. The Resource Server requires authentication. The MVC Clients gets an authorization code from the Authorization Server and Redirects the user to the Authorization Server for authentication. Finally the MVC Clients exchanges the authorization code for a Access token to Access the Resource Server. This is the Authorization code flow as described by the OAuth 2 protocol. This works fine.
Now, I have the requirement to make a Controller of the MVC Client itself require Authentication. I can not find a tutorial for this.
I added
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
to my Startup.Auth.cs.
I assume, I need to setup the Options to Redirect to the Authorization Server. I can also set the Provider on the Options:
app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
});
But I am also stuck on implementing the events of the Provider.
Can anybody guide me in the right direction? Or are there any tutorials which might help me?
I ended up with a solution based on these two articles from Brock Allen:
http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/
http://brockallen.com/2014/01/09/a-primer-on-external-login-providers-social-logins-with-owinkatana-authentication-middleware/
The fundemental idea is to register two authentication Middlewares. An active Cookie-Authentication and a passive OAuthBearer-Authentication. In Startup.Auth.cs they are added like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/ExternalLogin/Login"),
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ExternalBearer,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
You also add an ExternalLogin-Controller. Its Login-method has to redirect the user to the Login-page of your Authorization Server to get the authorization code. You have to supply a callback function where you will process the authorization code.
public async Task<ActionResult> Login(string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl) && Request.UrlReferrer != null)
returnUrl = Server.UrlEncode(Request.UrlReferrer.PathAndQuery);
if (Url.IsLocalUrl(returnUrl) && !string.IsNullOrEmpty(returnUrl))
_returnUrl = returnUrl;
//callback function
_redirectUrl = Url.Action("AuthorizationCodeCallback", "ExternalLogin", null, Request.Url.Scheme);
Dictionary<string, string> authorizeArgs = null;
authorizeArgs = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"response_type", "code"}
,{"scope", "read"}
,{"redirect_uri", _redirectUrl}
// optional: state
};
var content = new FormUrlEncodedContent(authorizeArgs);
var contentAsString = await content.ReadAsStringAsync();
return Redirect("http://localhost:64426/oauth/authorize?" + contentAsString);
}
In your callback-function you exchange the authorization code for an access token (plus refresh token) challenge your passive OAuthBearer-authentication Middleware and signin with the Access token as your Cookie.
public async Task<ActionResult> AuthorizationCodeCallback()
{
// received authorization code from authorization server
string[] codes = Request.Params.GetValues("code");
var authorizationCode = "";
if (codes.Length > 0)
authorizationCode = codes[0];
// exchange authorization code at authorization server for an access and refresh token
Dictionary<string, string> post = null;
post = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"client_secret", "ClientSecret"}
,{"grant_type", "authorization_code"}
,{"code", authorizationCode}
,{"redirect_uri", _redirectUrl}
};
var client = new HttpClient();
var postContent = new FormUrlEncodedContent(post);
var response = await client.PostAsync("http://localhost:64426/token", postContent);
var content = await response.Content.ReadAsStringAsync();
// received tokens from authorization server
var json = JObject.Parse(content);
_accessToken = json["access_token"].ToString();
_authorizationScheme = json["token_type"].ToString();
_expiresIn = json["expires_in"].ToString();
if (json["refresh_token"] != null)
_refreshToken = json["refresh_token"].ToString();
//SignIn with Token, SignOut and create new identity for SignIn
Request.Headers.Add("Authorization", _authorizationScheme + " " + _accessToken);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
ctx.Authentication.SignOut(DefaultAuthenticationTypes.ExternalBearer);
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
var ctxUser = ctx.Authentication.User;
var user = Request.RequestContext.HttpContext.User;
//redirect back to the view which required authentication
string decodedUrl = "";
if (!string.IsNullOrEmpty(_returnUrl))
decodedUrl = Server.UrlDecode(_returnUrl);
if (Url.IsLocalUrl(decodedUrl))
return Redirect(decodedUrl);
else
return RedirectToAction("Index", "Home");
}
I hope this is useful for someone who is implementing the OAuth authorization code flow in his MVC 5 application.
I used official sample MVC Implicit Client which I believe is the correct authentication flow for MVC application.
For authorization I used this getting started, especially the part about infinite loop when roles are specified [Authorize(Roles = "Foo,Bar")] and user is authenticated but doesn't own any of these.

Resources