Identity Server 4 - Autenticate with refreshed token - identityserver4

I'm approaching with identity Server 4 recenly and after read the documentation and try the examples project i have some difficulty in thi scenario:
1 - Logged-in with a provider (ex. Google)
2 - I ask to my IS4 a token passing the code_token of Google
3 - i obtain a new valid token from my IS4
4 - Store the new token and use it for autenticate from now
My problem is in the point 4 because i don't know how store into MVC application the new token
HttpContext.GetTokenAsync("access_token")
This call i every time null.
AuthenticationProperties authProp = new AuthenticationProperties();
authProp.StoreTokens(new List<AuthenticationToken>
{
new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = newIdToken },
new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken },
new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken },
new AuthenticationToken { Name = OpenIdConnectParameterNames.AcrValues, Value = string.Format("idp:{0} email:{1}", model.Provider, model.Email) }
});
SignIn(new ClaimsPrincipal(identity), authProp, "oidc");
After do that saving the new tokens and adding the provider into the AcrValues
If i go in a page with [Authorize] the call made to the IS4 is without AcrValues and return no logged user.
What I missed on the way?
Thanks

Related

Is there a way to avoid using the redirected form in Spring OAuth2 Authorization server when trying to get Authorization code? [duplicate]

I'm trying to create a local Java-based client that interacts with the SurveyMonkey API.
SurveyMonkey requires a long-lived access token using OAuth 2.0, which I'm not very familiar with.
I've been googling this for hours, and I think the answer is no, but I just want to be sure:
Is it possible for me to write a simple Java client that interacts with the SurveyMonkey, without setting up my own redirect server in some cloud?
I feel like having my own online service is mandatory to be able to receive the bearer tokens generated by OAuth 2.0. Is it possible that I can't have SurveyMonkey send bearer tokens directly to my client?
And if I were to set up my own custom Servlet somewhere, and use it as a redirect_uri, then the correct flow would be as follows:
Java-client request bearer token from SurveyMonkey, with
redirect_uri being my own custom servlet URL.
SurveyMonkey sends token to my custom servlet URL.
Java-client polls custom servlet URL until a token is available?
Is this correct?
Yes, it is possible to use OAuth2 without a callback URL.
The RFC6749 introduces several flows. The Implicit and Authorization Code grant types require a redirect URI. However the Resource Owner Password Credentials grant type does not.
Since RFC6749, other specifications have been issued that do not require any redirect URI:
RFC7522: Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC8628: OAuth 2.0 Device Authorization Grant
In any case, if the grant types above do not fit on your needs, nothing prevent you from creating a custom grant type.
Not exactly, the whole point of the OAuth flow is that the user (the client you're accessing the data on behalf of) needs to give you permission to access their data.
See the authentication instructions. You need to send the user to the OAuth authorize page:
https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>
This will show a page to the user telling them which parts of their account you are requesting access to (ex. see their surveys, see their responses, etc). Once the user approves that by clicking "Authorize" on that page, SurveyMonkey will automatically go to whatever you set as your redirect URI (make sure the one from the url above matches with what you set in the settings for your app) with the code.
So if your redirect URL was https://example.com/surveymonkey/oauth, SurveyMonkey will redirect the user to that URL with a code:
https://example.com/surveymonkey/oauth?code=<auth_code>
You need to take that code and then exchange it for an access token by doing a POST request to https://api.surveymonkey.net/oauth/token?api_key=<your_api_key> with the following post params:
client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code
This will return an access token, you can then use that access token to access data on the user's account. You don't give the access token to the user it's for you to use to access the user's account. No need for polling or anything.
If you're just accessing your own account, you can use the access token provided in the settings page of your app. Otherwise there's no way to get an access token for a user without setting up your own redirect server (unless all the users are in the same group as you, i.e. multiple users under the same account; but I won't get into that). SurveyMonkey needs a place to send you the code once the user authorizes, you can't just request one.
You do need to implement something that will act as the redirect_uri, which does not necessarily need to be hosted somewhere else than your client (as you say, in some cloud).
I am not very familiar with Java and Servelets, but if I assume correctly, it would be something that could handle http://localhost:some_port. In that case, the flow that you describe is correct.
I implemented the same flow successfully in C#. Here is the class that implements that flow. I hope it helps.
class OAuth2Negotiator
{
private HttpListener _listener = null;
private string _accessToken = null;
private string _errorResult = null;
private string _apiKey = null;
private string _clientSecret = null;
private string _redirectUri = null;
public OAuth2Negotiator(string apiKey, string address, string clientSecret)
{
_apiKey = apiKey;
_redirectUri = address.TrimEnd('/');
_clientSecret = clientSecret;
_listener = new HttpListener();
_listener.Prefixes.Add(address + "/");
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
}
public string GetToken()
{
var url = string.Format(#"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
HttpUtility.UrlEncode(#"http://localhost:60403"));
System.Diagnostics.Process.Start(url);
_listener.Start();
AsyncContext.Run(() => ListenLoop(_listener));
_listener.Stop();
if (!string.IsNullOrEmpty(_errorResult))
throw new Exception(_errorResult);
return _accessToken;
}
private async void ListenLoop(HttpListener listener)
{
while (true)
{
var context = await listener.GetContextAsync();
var query = context.Request.QueryString;
if (context.Request.Url.ToString().EndsWith("favicon.ico"))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
else if (query != null && query.Count > 0)
{
if (!string.IsNullOrEmpty(query["code"]))
{
_accessToken = await SendCodeAsync(query["code"]);
break;
}
else if (!string.IsNullOrEmpty(query["error"]))
{
_errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
break;
}
}
}
}
private async Task<string> SendCodeAsync(string code)
{
var GrantType = "authorization_code";
//client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.surveymonkey.net");
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
formData.Add(new KeyValuePair<string, string>("code", code));
formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
return null;
}
var data = await response.Content.ReadAsStringAsync();
if (data == null)
return null;
Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return(tokenInfo["access_token"]);
}
}

Add additional query parameters to the Azure B2C login url

I'm implementing Azure B2C to a .NET MVC app, and I need to add an extra query parameter to the login url.
Here's how I've set it up in the startup.cs
var openIdConnectAuthenticationOptions = new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid",
ResponseType = "id_token",
};
app.UseOpenIdConnectAuthentication(
openIdConnectAuthenticationOptions
);
And when someone tries to visit an [authorized] tagged page, it sends them to this b2c url:
https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_custom_signin&client_id=0000-000000-000-00&redirect_uri=https://localhost&response_type=id_token&scope=openid&x-client-SKU=ID_NET461&x-client-ver=5.3.0.0
However, I need to add an extra query parameter onto the end, "&appId=000-000-000", so the resulting login URL is:
https://mytenant.b2clogin.com/mytenant.onmicrosoft.com/oauth2/v2.0/authorize?p=b2c_custom_signin&client_id=0000-000000-000-00&redirect_uri=https://localhost&response_type=id_token&scope=openid&x-client-SKU=ID_NET461&x-client-ver=5.3.0.0 &appId=000-000-000
how would I go about doing this?
I'm afraid you could not add the appId parameter, but I recommend to make use of the state parameter. You could use this parameter to send the value of appid as part of request and it gets returned back in response.
For more details, see here.

Web api 2 oauth2 expiration sliding

i am building a SPA using Angular JS and web API2, use Oauth2 for authentication. My issue, token'expiration is fixed, such as 20 minutes. So how can we redirect to logion page if user does not have any request in 20 minutes?
Refresh token does not work because system will auto refresh token although user does not have any action in valid time.
Cheers,
You don't need to control timeout in client app.
When the client do a request to the resource server, the resource server validates the access token and if it's expired returns a 401 - Unauthorized response.
When the client gets the 401 from the resource server, needs to obtain a new access token from the authorization server, either using the resource owner credentials or the refresh token.
This is the behaviour specified by the OAuth 2.0 protocol.
Please let me know if you need a deeper explanation.
I use an AuthorizeAttribute and override OnAuthorization
public override void OnAuthorization(HttpActionContext actionContext)
{
string token = string.Empty;
AuthenticationTicket ticket;
//retrieve the token the client sent in the request...
token = (actionContext.Request.Headers.Any(x => x.Key == "Authorization")) ? actionContext.Request.Headers.Where(x => x.Key == "Authorization").FirstOrDefault().Value.SingleOrDefault().Replace("Bearer ", "") : "";
//Your OAuth Startup class may be called differently...
ticket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(token);
//verification using the ticket's properties. When it was set to expire (ExpiresUtc) or whatever other properties you may have appended to it's dictionnary.
//if verification fails..
//actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, "Verification failed.");
//return;
//Otherwise, send a new token with an extended expiration date...
AuthenticationProperties refreshTokenProperties = new AuthenticationProperties(ticket.Properties.Dictionary)
{
IssuedUtc = ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddMinutes(20)
};
AuthenticationTicket newToken = new AuthenticationTicket(ticket.Identity, refreshTokenProperties);
string newTokenHash = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(newToken);
//add the new token to request properties. Can't add it to the header here, because creating response/response headers here will prevent process from proceeding to called controller method.
actionContext.Request.Properties.Add(new KeyValuePair<string, object>("Token", newTokenHash));
}
Then chain it with an ActionFilterAttribute filter:
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (actionExecutedContext.Response == null)
return;
var objectContent = actionExecutedContext.Response.Content as ObjectContent;
//the token we put in the filter above...
string tokenHash = (actionExecutedContext.Request.Properties.Any(x => x.Key == "Token")) ? (string)actionExecutedContext.Request.Properties.Where(x => x.Key == "Token").FirstOrDefault().Value : "";
}
You can either append a new header to the response, put in the JSON payload response or add it as a response cookie. Then you make your client use this new hash when requesting any other resource, that way the expiration will slide an extra 20 mins everytime.
You can register these filter attributes globally in App_Start/WebApiConfig.cs
config.Filters.Add(new ClassExtendingAuthorizeAttribute());
config.Filters.Add(new ClassExtendingActionFilterAttribute());
But as mentioned by jumuro, you could have your client simply use the refresh token. Depends if you want to your back-end or front-end to do most of the leg work.
Hope it helps.

Identity server claims asp.net API

I'm currently writing an angular application that first authenticates against think texture identityserver3.
This works fine, and I receive the bearer token without any issues.
When I use my token on an call to my API, I'm authenticated. I can see my userid, but have lost my claims (username, roles,...).
What do I have to do for transferring my claims with my token, or getting the roles from the identityserver?
You can tell Identity Server to include specific claims in an access token by adding that claim to your API's Scope.
Example:
var apiScope = new Scope {
Name = "myApi",
DisplayName = "My API",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim> {
new ScopeClaim("myClaimType")
}
};
You can also use the AlwaysIncludeInIdToken property of ScopeClaim to include the claims in identity tokens as well as access tokens.
See https://identityserver.github.io/Documentation/docsv2/configuration/scopesAndClaims.html for more info.
We are doing something very similar using MS Web API 2 and a Thinktecture Identity Server v3.
To verify the user's claims we created an Authentication Filter, and then called the Identity server directly to get the user's claims. The bearer token only grants authentication and it is up to the API to get the claims separately.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
string identityServerUrl = WebConfigurationManager.AppSettings.Get("IdentityServerUrl") + "/connect/userinfo";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = actionContext.Request.Headers.Authorization;
var response = httpClient.GetAsync(identityServerUrl).Result;
if (response.IsSuccessStatusCode)
{
string responseString = response.Content.ReadAsStringAsync().Result;
Dictionary<string, string> claims = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseString.ToLower());
... Do stuff with your claims here ...
}
}
}

How to get notify for new message using TweetSharp ListDirectMessageReceived() method?

I am using TweetSharp for making a small demo desktop application for twitter and I am using the below code for getting all the messages of a login user:-
TwitterService service = new TwitterService("ConsumerKey", "ConsumerSecretKey");
// Step 1 - Retrieve an OAuth Request Token
OAuthRequestToken requestToken = service.GetRequestToken();
// Step 2 - Redirect to the OAuth Authorization URL
Uri uri = service.GetAuthorizationUri(requestToken);
Process.Start(uri.ToString());
// Step 3 - Exchange the Request Token for an Access Token
Console.Write("Enter Pin:: ");
string verifier = Console.ReadLine(); // <-- This is input into your application by your user
OAuthAccessToken access = service.GetAccessToken(requestToken, verifier);
// Step 4 - User authenticates using the Access Token
service.AuthenticateWith(access.Token, access.TokenSecret);
string message = null;
List<string> messages = new List<string>();
IEnumerable<TwitterDirectMessage> directMessages = service.ListDirectMessagesReceived(new ListDirectMessagesReceivedOptions());
//Fetch all current direct message:
foreach (TwitterDirectMessage directMessage in directMessages)
{
//Write All Messages in Console
Console.WriteLine(directMessage.Text);
}
Above foreach loop will show all the messages but my problem is that when any other user send me new message then how can notifying me for new message.
Please help me!
Any Help Appreciated!
Thanx!
This TweetSharp code defines the library methods.

Resources