I have an assignment to access a web API to get a token and use this token as an input to call another web API. Token expires after 60 seconds so I have to again call first web API to get new token and then use this new token to access my second web API.
In normal scenario it is working fine but some time second API takes more time to execute and token expires.
I have used HttpClient class to call web API. How should I handle token expiration time constraint before calling another web API. I want to make it more generic.
I have written following code.
public override bool CallAPI()
{
try
{
bool isSPAdded = false;
int counter = 0;
do
{
Uri uri = new Uri(_settings.HostUrl + "/auth/realms/master/protocol/openid-connect/token");
var requestContent = string.Format("username={0}&password={1}&client_id=admin-cli&grant_type=password", _settings.UserName, _settings.Password);
StringContent _requestContent = new StringContent(requestContent, Encoding.UTF8, "application/x-www-form-urlencoded");
_response = _client.PostAsync(uri, _requestContent).Result;
if (_response.IsSuccessStatusCode)
{
Uri uri = new Uri(String.Format(_settings.HostUrl + "/auth/admin/realms/{0}/clients", Node.OrgId));
_requestContent = new StringContent(response.Content.ReadAsStringAsync().Result, Encoding.UTF8, "application/json");
_response = _client.PostAsync(uri, _requestContent).Result;
if (_response.IsSuccessStatusCode)
{
isSPAdded = _response.IsSuccessStatusCode;
break;
}
}
counter++;
} while (_response.StatusCode == HttpStatusCode.Unauthorized && _response.Content.ReadAsStringAsync().Result == "Bearer" && counter < 2);
return isSPAdded;
}
}
I have used do while loop to retry it if token expires.
Please suggest the best approach for same.
Related
I am investigating how to possibly authenticate to a Kubernetes 1.13 cluster with OpenID Connect and Keycloak. I am new to this area.
This YouTube video ("Use Open ID Connect for Kubernetes API server") accomplishes part of what I want. An id token is initially obtained by making a HTTP request (with curl) to Keycloak citing grant type password. The resulting token is then subsequently used in further HTTP requests to the Kubernetes API. This works but has the disadvantage that clients directly handle users' permanent credentials.
Would it not be better if the token were issued by a secure web page that also required authentication via Keycloak (this time with grant type authorization code) and did nothing else but displaying a new token? Such tokens (transient credentials) could then e.g. be manually copied into kubeconfigs for further use?
Does Keycloak provide such interactive web pages (next to the REST endpoints for obtaining tokens programatically) or is this out of scope? If the second, are there other standard components for such tasks?
UPDATE This illustration from the Kubernetes documentation perhaps makes more clear what I am seeking. In step 1 a user should log into the Identity provider to obtain tokens which can then be configured into kubectl. Does Keycloak support this step, i.e. offer a web page where users could log in to obtain their tokens?
If I am able to understand your question ,so you want to get the accesstoken via Java code so here is code you can write and call
String obtainAccessToken = obtainAccessToken(username, password);
putRequest.addHeader("Authorization", "bearer " + obtainAccessToken);
putRequest.addHeader("content-type", MediaType.APPLICATION_JSON);
Here is the method you should call
public String obtainAccessToken(String UserName, String pwd)
{
AuthzClient authzClient = AuthzClient.create(configuration);
AccessTokenResponse accessTokenResponse = authzClient.obtainAccessToken(UserName, pwd);
String token = accessTokenResponse.getToken();
return token;
}
Here is the get realm method
public Response getAllRealms() {
ObjectMapper mapper = JacksonObjectMapperProvider.getObjectMapper();
CloseableHttpResponse response = null;
List<SureRealmRepresentation> realmList = new ArrayList<SureRealmRepresentation>();
int status;
try {
String urlGetAllRealms = URL + "/admin/realms";
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet getRequest = new HttpGet(urlGetAllRealms);
String obtainAccessToken = obtainAccessToken(username, password);
getRequest.addHeader("Authorization", "bearer " + obtainAccessToken);
getRequest.addHeader("content-type", MediaType.APPLICATION_JSON);
response = httpclient.execute(getRequest);
status = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (status == 200) {
RealmRepresentation[] realmArray = mapper.readValue(responseBody, RealmRepresentation[].class);
}
catch (Exception e) {
if (e instanceof Exception) {
throw (Exception) e;
} else {
throw ErrorHandler.wrap(new Exception("EroorType : "+ e.toString()));
}
}
So I've built and published a new website that uses Azure B2C as the authentication mechanism.
What I found was that the login and sign would work fine for a while. But after a period of time, say couple of hours after visiting the site post deployment, I would find that on login or signup, after successful authentication, instead of being redirected back to the return url set up in the b2c configuration, my browser would get caught between an infinite loop between the post authentication landing page that is protected with an authorise attribute and the Azure B2C Login page, before finally finishing with Http 400 error message with the message - Bad Request - Request too long.
I did some googling around this and there are number of posts that suggest that the problem is with the cookie, and that deleting the cookie should resolve the issue. This is not the case. The only thing I have found to fix this is restarting the application on the webserver, or waiting say 24 hours for some kind of cache or application pool to reset. Anyone has any ideas what's going on here?
Ok, I think I may have found the answer.
Looks like there is an issue with Microsoft.Owin library and the way it sets cookies. Writing directly to System.Web solves this problem according to this article.
There are three suggested solutions:
Ensure session is established prior to authentication: The conflict between System.Web and Katana cookies is per request, so it may be possible for the application to establish the session on some request prior to the authentication flow. This should be easy to do when the user first arrives, but it may be harder to guarantee later when the session or auth cookies expire and/or need to be refreshed.
Disable the SessionStateModule: If the application is not relying on session information, but the session module is still setting a cookie that causes the above conflict, then you may consider disabling the session state module.
Reconfigure the CookieAuthenticationMiddleware to write directly to System.Web's cookie collection.
I will opt for the third option, which is to overwrite the default Cookie AuthenticationMiddleware, as they have suggested below.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
public class SystemWebCookieManager : ICookieManager
{
public string GetRequestCookie(IOwinContext context, string key)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
var cookie = webContext.Request.Cookies[key];
return cookie == null ? null : cookie.Value;
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
bool pathHasValue = !string.IsNullOrEmpty(options.Path);
bool expiresHasValue = options.Expires.HasValue;
var cookie = new HttpCookie(key, value);
if (domainHasValue)
{
cookie.Domain = options.Domain;
}
if (pathHasValue)
{
cookie.Path = options.Path;
}
if (expiresHasValue)
{
cookie.Expires = options.Expires.Value;
}
if (options.Secure)
{
cookie.Secure = true;
}
if (options.HttpOnly)
{
cookie.HttpOnly = true;
}
webContext.Response.AppendCookie(cookie);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
AppendResponseCookie(
context,
key,
string.Empty,
new CookieOptions
{
Path = options.Path,
Domain = options.Domain,
Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
});
}
}
I will give that a crack, and post my results back here.
I'm trying to build an application in such a way that NodeJS serves as the backend with all business logic exposing JSON REST services to be consumed by the angular 4 app which is nothing but a dumb client. So far so good, however I'm having a hard time figuring the Session management.
I found that token based authentication is a way to go since you might one day be serving mobile apps however, I have a problem: if I go with JSONWebToken strategy on the server side with token expiration set to half an hour, then my client will need to re authenticate it self after half an hour which doesn't seem like a good fit, because then it may force the user to sign in again that is already working on the client application which is not how any web app works. Should I also need to maintain session management at Angular level and auto sign in if my token expires on server but then it violates the principle of a dumb client or I should scrap it altogether implement sessions at NodeJS it self? Another thing is if I implement the WebTokenStrategy I found that for every request that comes from the client I'll be making a trip to database to verify a user which I can cache in session if I'm doing session management on NodeJS.
Last thing that I have a hard time figuring out is okay I can secure my resources on NodeJS but then I also need my routes and pages to be served depending on user rights in my client application, should I also store this information in the NodeJS database and serve by the same API server but I think this again violates the single responsibility principle or should there be another database for this client site route and user management.
Can someone suggest a good approach and if possible with examples?
Thanks.
No a JSON web token do not required a trip to the database since you encode the information you want on the payload. However you can implement a redis strategy if you want to be able to revoke them (For right changes for example). The signature part will be used by your server to ensure the authenticity (thanks to your server-side JWT secret).
You can also choose the expiration time you want. But if you want to limit it to 30 minutes you can also implement a renew strategy. (Ask for a new token before the old one will expire soon : The server will just deliver a new token with the same data encode. For the front end renew strategy you can use such a lib :
'use strict';
/**
* Helper class to decode and find JWT expiration.
*/
class JwtHelper {
urlBase64Decode(str) {
let output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0: { break; }
case 2: { output += '=='; break; }
case 3: { output += '='; break; }
default: {
throw 'Illegal base64url string!';
}
}
return this.b64DecodeUnicode(output);
}
// credits for decoder goes to https://github.com/atk
b64decode(str) {
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let output = '';
str = String(str).replace(/=+$/, '');
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
let bc = 0, bs, buffer, idx = 0;
// get next character
buffer = str.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
}
// https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map.call(this.b64decode(str), (c) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
decodeToken(token) {
let parts = token.split('.');
if (parts.length !== 3) {
throw new Error('JWT must have 3 parts');
}
let decoded = this.urlBase64Decode(parts[1]);
if (!decoded) {
throw new Error('Cannot decode the token');
}
return JSON.parse(decoded);
}
getTokenExpirationDate(token) {
let decoded;
decoded = this.decodeToken(token);
if (!decoded.hasOwnProperty('exp')) {
return null;
}
let date = new Date(0); // The 0 here is the key, which sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
}
isTokenExpired(token, offsetSeconds) {
let date = this.getTokenExpirationDate(token);
offsetSeconds = offsetSeconds || 0;
if (date == null) {
return false;
}
// Token expired?
return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
}
}
const jwtHelper = new JwtHelper();
const decodedData = jwtHelper.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ');
console.log(decodedData)
I am building a Universal Windows Platform (UWP) app that uses the Azure App Service Mobile App backend as well as the user's OneDrive account. I have 2 requirements for authentication:
If the user is logged in to their UWP device with a Microsoft account (e.g. Windows 10) then I don't want them to be presented with a login prompt (i.e. Single Sign On, re-using their Microsoft account credentials).
I want to have a single authentication event across Azure & OneDrive, i.e. the user authorises once and I re-use that token for both services.
I did this in Windows Phone 8 with an Azure Mobile Service by logging in with the Live SDK and then passing the returned token to the MobileServiceClient.LoginAsync() method, however I can't get this to work in UWP with an Azure Mobile App. When I call that same method I receive a 401 Unauthorised response.
I have associated my UWP app with the store and set up the
application at the Microsoft Account Developer Centre, including
adding the redirect URI from the Azure Mobile App.
I have set up the Azure App Service Mobile App, including adding the
Client ID & Secret from the Microsoft Account Developer Centre.
I have tried numerous ways to retrieve the token, including the
OnlineIdAuthenticator, WebAuthenticationCoreManager and
WebAuthenticationBroker. None has worked so far.
I currently use the following code in a class LiveAuthenticationService to retrieve an access token:
public async Task<bool> LoginAsync()
{
AccessToken = null;
bool success = false;
OnlineIdAuthenticator onlineIdAuthenticator = new OnlineIdAuthenticator();
EventWaitHandle waithandle = new ManualResetEvent(false);
OnlineIdServiceTicketRequest serviceTicketRequest = new OnlineIdServiceTicketRequest(scopes, "DELEGATION");
UserIdentity result = await onlineIdAuthenticator.AuthenticateUserAsync(serviceTicketRequest);
if (!string.IsNullOrWhiteSpace(result?.Tickets[0]?.Value))
{
currentUserId = result.SafeCustomerId;
AccessToken = result.Tickets[0].Value;
success = true;
waithandle.Set();
}
else
{
await logger.LogErrorAsync("Error signing in to Microsoft Live",
new Dictionary<string, string> { { "errorCode", result?.Tickets[0]?.ErrorCode.ToString() } });
}
waithandle.WaitOne(10000); //10 second timeout
return success;
}
And then this to attempt to login to my Azure Mobile App with that token, which uses LiveAuthenticationService from above:
private async Task RefreshUserIdAndAccessToken()
{
try
{
var tcs = new TaskCompletionSource<MobileServiceUser>();
var authService = new LiveAuthenticationService();
await UiDispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
try
{
await authService.LoginAsync();
var jsonAuthenticationToken = JObject.Parse(#"{""authenticationToken"": """ + authService.AccessToken + #"""}");
tcs.SetResult(await mobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, jsonAuthenticationToken));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
});
var user = await tcs.Task;
currentUserId = user.UserId;
AccessToken = user.MobileServiceAuthenticationToken;
}
catch (Exception ex)
{
await logger.LogExceptionAsync(ex,
Constants.LOGGING_DATAKEY_REFRESHACCESSTOKENFAILURE,
currentUserId);
currentUserId = null;
AccessToken = null;
}
}
As stated this results in a 401 Unauthorised response from Azure. I have run Fiddler and the request seems to be correct, the expected authentication token is included in a JSON payload with the request.
UPDATE
One thing I can see is that the token issued by the code above is almost 900 characters long, all in the form YnElFkAAcK8bRSQab/FK+PT5n/wA4CPU..., while the token issued if I let Azure Mobile App handle the authentication, i.e. call MobileServiceClient.LoginAsync() without passing a token, is only about 350 characters long and in the form hbGciOi.eyJmdWWxsIiwiRGJn... (notice the period towards the beginning).
This issue is really causing me problems now. I can't release the app without the authentication working and I can't figure out how to fix it. Any help will be appreciated.
This was a tough one for me to solve as I was facing this problem too.
The most important part is the OnlineIdServiceTicketRequest the request should look like this:
var mobileServicesTicket = new OnlineIdServiceTicketRequest("https://yourmobileservice.azure-mobile.net/", "JWT");
Note that we are specifying your endpoint and also requesting a JWT token instead of delegation. This will get the 350ish character token you were looking for.
Here is a full code sample of what I'm doing:
public async Task<bool> LoginAsync()
{
var authenticator = new Windows.Security.Authentication.OnlineId.OnlineIdAuthenticator();
var mobileServicesTicket = new Windows.Security.Authentication.OnlineId.OnlineIdServiceTicketRequest("https://yourendpoint.azure-mobile.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 res = await _mobileServiceClient.LoginWithMicrosoftAccountAsync(accessToken.Value);
return true;
}
else
{
return false;
}
}
_mobileServiceClient is injected into the class and is a reference to Microsoft.WindowsAzure.MobileServices.MobileServiceClient object within the WindowsAzure.MobileServices library.
I actually ended up writing a blog article about this problem here http://jshapland.com/single-sign-on-with-azure-mobile-services-in-a-uwp-app/
I'm trying to figure out how to support ws-security as authentication mechanism in SS.
My goal is to have all DTO handled in json,xml,saop11,saop12(that part has been achieved following the SS documentation) and supporting multiple auth providers including one based on ws-security.
DTOs should not be affected at all by the authentication mechanism.
In case the DTO will be sent using saop12, the soap message will be the the call sample generated by the metadata endpoint(soap envelope + soap body) plus a soap header including the ws-security element for the WS-Security Username Authentication. A dedidcated "soap auth provider" should inspect the message, use the soap header -> security element and perform the authentication.
Along with the soap auth provider, I may have other built-in auth mechanism that may used for json message and/or other formats.
Exists a SS auth provider based on ws-security that I'm not aware of?
Any guidelines, suggestions, thoughts to implement it?
At the present than my solution
//AppHost
Plugins.Add(new AuthFeature(() => new CustomAuthUserSession(),
new IAuthProvider[] {
new CustomCredentialsAuthProvider(),
new SoapMessageAuthProvider(),
}
));
// required by the SoapMessageAuthProvider to inspect the message body serching for ws-security element
PreRequestFilters.Add((httpReq, httpRes) =>
{
httpReq.UseBufferedStream = false;
});
I based the SoapMessageAuthProvider on the built-in BasicAuthProvider.
Since the SoapMessageAuthProvider requires to inspect the incoming message on each call serching for ws-security element, I implemented IAuthWithRequest
public void PreAuthenticate(IRequest req, IResponse res)
{
//Need to run SessionFeature filter since its not executed before this attribute (Priority -100)
SessionFeature.AddSessionIdToRequestFilter(req, res, null);
var userPass = ExtractSoapMessageUserNameCredentials(req);//req.GetBasicAuthUserAndPassword();
if (userPass != null)
{
var authService = req.TryResolve<AuthenticateService>();
//var response = authService.Post(new Authenticate
//{
// provider = Name,
// UserName = userPass.Value.Key,
// Password = userPass.Value.Value
//});
authService.Request = req;
var session = authService.GetSession(false);
var userName = userPass.Value.Key;
//Add here your custom auth logic (database calls etc)
var userAuth = new UserAuth();
userAuth.Id = 10;
userAuth.UserName = userName;
var holdSessionId = session.Id;
session.PopulateWith(userAuth); //overwrites session.Id
session.Id = holdSessionId;
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.UserAuthName = userName;
}
}
//called by CustomAuthUserSession.IsAuthorized
// to be reviewed to keep isolated from other providers
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
if (request != null)
{
if (!LoginMatchesSession(session, request.UserName))
{
return false;
}
}
return !session.UserAuthId.IsNullOrEmpty();//filled by PreAuthenticate
}
the custom session calls each provider, including the SoapMessageAuthProvider that meanwhile, through the PreAuthenticate method, filled out the session with authenticated user data.
public class CustomAuthUserSession : AuthUserSession
{
public override bool IsAuthorized(string provider)
{
var tokens = ProviderOAuthAccess.FirstOrDefault(x => x.Provider == provider);
return AuthenticateService.GetAuthProvider(provider).IsAuthorizedSafe(this, tokens);
}
...
}
I need to make sure the soap provider will be always invoked for soap message w/ ws-security and the call should not be authenticated by other providers:
- user get authentication through the CustomCredentialsAuthProvider(cookie based)
- user call the service supply json message within the web request that carries the auth cookie
- a further call sends a soap message carrying the same auth cookie: since the message is in soap format and includs the soap header ws-security, the call should be authenticated only using the soap provider using the soap header ws-security within the message.
I understand that a weird scenario, but I'm trying to understand how to accomplish it.
My guess that happends through the ServiceStack.AuthenticateAttribute line 72
matchingOAuthConfigs.OfType<IAuthWithRequest>()
.Each(x => x.PreAuthenticate(req, res));