How to handle Stormpath ID Site JWT response - c#

I'm trying to create an ASP.NET application with Stormpath ID Site authorization. I create request and response action and successfully got the account.
But what next? How to tell the application that the user is authenticated?
public async Task<RedirectResult> Callback(string jwtResponse)
{
var client = Request.GetStormpathClient();
var app = await client.GetApplicationAsync(appUrl);
var requestDescriptor = HttpRequests.NewRequestDescriptor()
.WithMethod("GET")
.WithUri("http://localhost:50084/Auth/Callback?jwtResponse=" + jwtResponse)
.Build();
var idSiteListener = app.NewIdSiteAsyncCallbackHandler(requestDescriptor);
var accountResult = await idSiteListener.GetAccountResultAsync();
var account = accountResult.GetAccountAsync().Result; //Account
//What I must do here to tell application that user is authenticated
return Redirect("/");
}

Instead of getting the account from the ID Site response, you could exchange the JWT for a Stormpath access token:
public async Task<RedirectResult> Callback(string jwtResponse)
{
var client = Request.GetStormpathClient();
var app = await client.GetApplicationAsync(appUrl);
var exchangeRequest = new StormpathTokenGrantRequest
{
Token = jwtResponse
});
var grantResponse = await application.ExecuteOauthRequestAsync(exchangeRequest);
// Return grantResponse.AccessTokenString in a secure HTTPOnly cookie, or as a JSON response
}
If you use the Stormpath ASP.NET plugin, you can enable ID Site and this will be handled for you automatically.
Disclaimer: I'm the package author.

Related

ASP.NET Identity - Get Saved third-party access tokens

I have an app that will operate almost entirely on Spotify OAuth, that will have features to alter the playback of your music.
I'm able to get Spotify OAuth working perfectly such that I can log into my app, but after I've logged in, I need access to the current user's spotify access_token so that I can forward it to my spotify requests.
I followed this guide from ms to try to save the tokens: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-6.0&tabs=visual-studio
And I have tried all these ways to then save that token into the HttpContext such that I can access it:
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
var spotifyAccessToken = tokens.FirstOrDefault(x => x.Name == "access_token").Value;
tokens.Add(new AuthenticationToken()
{
Name = "SpofityAccessToken",
Value = spotifyAccessToken
});
//store all the tokens as directed by MS
ctx.Properties.StoreTokens(tokens);
//store the properties into the HttpContext in 2 different ways
ctx.HttpContext.Items["Properties"] = ctx.Properties;
ctx.HttpContext.Features.Set(ctx.Properties);
//try adding a claim to the user
ctx.Identity.AddClaims(new[] { new Claim("SpotifyAccessToken", spotifyAccessToken) });
return Task.CompletedTask;
};
The problem I'm having is how do I then get this token out? all of these methods are not working:
[HttpGet]
public async Task Get()
{
await HttpContext.SignInAsync(User);
// try to re-run Authenticate, even though i'm already in an [Authorize] controller
var res = await HttpContext.AuthenticateAsync();
//props2 does not have the tokens i set
var props2 = res.Properties;
//props comes back null
var props = HttpContext.Features.Get<AuthenticationProperties>();
//claims has no SpotifyAccessToken claim
var claims = User.Claims.ToList();
var token = "hard-coded";
//here is where i need the token to then query spotify
var client = new SpotifyAPI.Web.SpotifyClient(token);
var res2 = await client.Player.GetCurrentPlayback();
}
I feel like I've tried everything, what am i doing wrong?
This is in a .NET 6 blazor wasm, .net core hosted app.
Also tried the solutions here to no avail Get AuthenticationProperties in current HttpRequest after HttpContext.SignInAsync
signInManager.UpdateExternalAuthenticationTokensAsync adds the the authentication tokens in [dbo].[AspNetUserTokens]
External login is where I call it:
// Sign in the user with this external login provider if the user already has a login.
var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: true, bypassTwoFactor: true);
if (signInResult.Succeeded)
{
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
Later on you can get it by using :
var token = await userManager
.GetAuthenticationTokenAsync(user, "Spotify", "access_token");
var expiresAtStr = await userManager
.GetAuthenticationTokenAsync(user, "Spotify", "expires_at");
If the token is stored in the Cookie then you can access the various tokens using:
string accessToken = await HttpContext.GetTokenAsync("access_token");
string idToken = await HttpContext.GetTokenAsync("id_token");
string refreshToken = await HttpContext.GetTokenAsync("refresh_token");
string tokenType = await HttpContext.GetTokenAsync("token_type");
string accessTokenExpire = await HttpContext.GetTokenAsync("expires_at");
However, you can not store data in ctx.HttpContext and assume it will be persisted across requests. either you sign-in the user using the cookie middleware or you store the tokens in the UserSession object.
See this article on how to configure and store data in the session, that will be persisted across requests.
Session and state management in ASP.NET Core
If you configure it properly, then you can use it like:
HttpContext.Session.SetString("token", token.Trim());

Azure AD B2C with WebApi2- calling GraphAPI after authentication

I have a WebApi2 app which servers as api for my app frontend. Now i want to use AD B2C to manage my users - let's say I want to differentiate them by their roles (admin or customer) and for that i created two b2c users groups accordingly. When user logs in i want to display different things for users with different roles (groups).
I'm using this example to setup Startup.Auth.cs in my WebApi2 project:
var tvps = new TokenValidationParameters
{
ValidAudience = clientId,
AuthenticationType = signUpSignInPolicy,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, defaultPolicy))),
});
From what I have read b2c doesn't return user's grups in claims for now. Some people suggested I need to call GraphApi after obtaining token to fetch these groups and add them to user's claims:
private static async Task<string> GetGroups(string token, string userId)
{
using (var client = new HttpClient())
{
var requestUrl = $"https://graph.microsoft.com/v1.0/users/{userId}/memberOf?$select=displayName";
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
This is where I've stuck. How can I inject my code to get token for calling graph? I've messed with OAuthBearerAuthenticationOptions.Provider:
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = (context) =>
{
// var token = ??
// var userId = <get from context's claims>
// var groups = GetGroups(token, userId);
// <add to claims>
return Task.CompletedTask;
}
},
...but I don't know how to get to token. Maybe that's wrong from the start and I need another approach?
Customer's token cannot be used to call AADGraph/MSGraph Apis. To get token to call graph apis in an automated way, we need app-only access. We need to configre an app in the tenant, the crendetial of which are used to get a token. That token can then be used to call memberOF Api (or any other api which does or require user information to be there)
Here is the sample and explaination of how to call AAD Graph apis in a B2C dependent service.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet

How to implement refresh token workflow into OAUTH workflow in MVC C# app?

I am new to OAUTH. I have been working on implementing OAUTH into my MVC c# application to access ping federate. After much research, and failed attempt at using the ping federate nuget, I came across this link that finally gave some clarity to the full process with a coding example. I have came across much generic examples of the endpoints i need to access but never a full workflow coding example. After implementing that code with some changes and was successful at signing in the ping user into my MVC app, I started doing more research about the refresh token. Questions...
Q. I know how to access a a refresh token, meaning I know which endpoint used to refresh the access token after I have authenticated the user in ping federate. But what is the refresh token used for? Is it used to extend my application's session once it ends? Or it used for if the user signs out of my application then they click the 'Sign in with Ping Federate' link on the login and not have them authenticate again as long as the refresh token is still valid?
Q. And if the refresh token is used for when after a user authenticates the first time, and I save the refresh token in the db and then user signs back using that 'Sign in with Ping Federate' link on my login back how can I know what user that is to lookup the refresh token in the db to give them access to my site without re-authenticating them with ping federate? Since when they come to that link 'Sign in with Ping Federate' I do not know who they are?
This is the below code that I am using, from user MatthiasRamp in the link i provided...I want to add my refresh token logic with the below 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);}
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");
}

Get NameIdentifier claim in Azure App Services using OnlineIdAuthenticator

I am using the OnlineIdAuthenticator class to get an authentication token in order to log in to Azure App Services, but I'm unable to get the NameIdentifier claim server side. I need the Id to uniquely identify a user and store it in a database.
I want to use the OnlineIdAuthenticator to enable single sign on, so the user isn't prompted to login every time he starts my app (a Windows Store App).
I use this method in my app to get the token and login:
protected override async Task<bool> LoginAsync()
{
var authenticator = new OnlineIdAuthenticator();
var mobileServicesTicket = new OnlineIdServiceTicketRequest("myapp.azurewebsites.net", "JWT");
var ticketRequests = new List<OnlineIdServiceTicketRequest>() { mobileServicesTicket };
var authResult = await authenticator.AuthenticateUserAsync(ticketRequests, CredentialPromptType.PromptIfNeeded);
if ((authResult.Tickets.Count == 1) && (authResult.Tickets[0].ErrorCode == 0))
{
var accessToken = authResult.Tickets[0];
var token = new JObject();
token.Add("authenticationToken", accessToken.Value);
var _mobileServiceClient = ServiceLocator.Current.GetInstance<IMobileServiceClient>();
var user = await _mobileServiceClient.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, token);
return true;
}
else
{
return false;
}
}
Server side I use this method to get the unique Id:
public static async Task<string> GetUserId(IPrincipal pricipal, HttpRequestMessage request)
{
ProviderCredentials credentials = await pricipal.GetAppServiceIdentityAsync<MicrosoftAccountCredentials>(request);
return credentials.Provider + ":" + credentials.Claims[ClaimTypes.NameIdentifier];
}
The Claims collection above contain these keys:
ver, iss, exp, uid, aud, urn:microsoft:appuri, urn:microsoft:appid
Everything else seems to work. I'm able to login with my MicrosoftAccount and I can call methods that require authentication, but I'm just not able to generate the user id.
How can I get the NameIdentifier in the Claims collection?

instaSharp oauthResponse Not work

I want to use instaSharp to use instagram api , get followers anp posts and ...
when I get code with callbackUrl I cant Send Requesttoken(code)
and my oauthResponse is null ...
this is my code :
async Task getaouth()
{
var clientId = ConfigurationManager.AppSettings["client_id"];
var clientSecret = ConfigurationManager.AppSettings["client_secret"];
var redirectUri = ConfigurationManager.AppSettings["redirect_uri"];
var realtimeUri = "";
InstagramConfig config = new InstagramConfig(clientId, clientSecret, redirectUri, realtimeUri);
Session.Add("InstaSharp.config", config);
// add this code to the auth object
var auth = new InstaSharp.OAuth(config);
// now we have to call back to instagram and include the code they gave us
// along with our client secret
var oauthResponse = await auth.RequestToken(code);
// tell the session that we are authenticated
//config.isAuthenticated = true;
Response.Write(r.ToString());
// both the client secret and the token are considered sensitive data, so we won't be
// sending them back to the browser. we'll only store them temporarily. If a user's session times
// out, they will have to click on the authenticate button again - sorry bout yer luck.
Session.Add("InstaSharp.AuthInfo", oauthResponse);
// all done, lets redirect to the home controller which will send some intial data to the app
//return RedirectToAction("Index", "Home");
Response.Write("");
}
after this my oauthResponse is null !
and after call this method
_users.GetSelf()
i get it :
An exception of type 'System.InvalidOperationException' occurred in InstaSharp.dll but was not handled in user code
Additional information: You are not authenticated
Have you already registered your test client application on your account at Instagram developers?
If you haven't, sing in with your account here, click on top-button "Manager clients" and add your test application to get the correct client ID and client secret informations.
Use this way,
Install latest InstaSharp and just do this:
private InstagramConfig _config;
public async Task< ActionResult> somename(string code)
{
if (code != null)
{
_config = new InstagramConfig(["InstgramClientId"],
["InstgramClientSecret"],
["InstgramRedirectUrl"]
);
var instasharp = new InstaSharp.OAuth(_config);
var authInfo = await instasharp.RequestToken(code);
var user = new InstaSharp.Endpoints.Users(_config, authInfo);
ViewBag.Username = user.OAuthResponse.User.Username;
ViewBag.Token = authInfo.AccessToken;
return View();
}
return View("name");
}
In your case, you have to make call to your method like:
var authresponse = await getaouth();
Make sure your calling function is async task.

Categories