How to use Expo AppAuth module with IdentityServer4 - react-native

I am trying to use the Expo AppAuth module to do authentication using IdentityServer4 in react native. Cant seem to get the redirectUri settings right. I'm getting an 'invalid redirect uri" error when i redirect to identityServer.
This is my client on identityserver
return new List<Client>
{
new Client
{
ClientName = "client",
ClientId = "client",
RequirePkce = true,
AllowedGrantTypes = GrantTypes.Code,
RequireClientSecret = false,
RequireConsent = true,
RedirectUris =
{
"host.exp.Exponent" //Is this correct
},
AllowOfflineAccess = true,
RefreshTokenUsage = TokenUsage.ReUse,
AllowedScopes = { "openid", "profile"},
}
};
My config settings for AppAuth are
const config = {
issuer: 'http://localhost:3000',
clientId: 'client',
scopes: ['profile', 'openid'],
redirectUri: "host.exp.Exponent"
}

You should specify redirectUri as the address value.
AppAuth Definitions:
async function _executeAsync(props: OAuthProps): Promise<TokenResponse> {
if (!props.redirectUrl) {
props.redirectUrl = getDefaultOAuthRedirect();
}
assertValidProps(props);
return await ExpoAppAuth.executeAsync(props);
}
export function getDefaultOAuthRedirect(): string {
return `${ExpoAppAuth.OAuthRedirect}:/oauthredirect`;
}

Related

Single sign out in identity server 4

###I am using identity server 4 for authentication for .net and angular apps.
if I log out from one client it does not log out from others.###
how can I delete the user session and implement single-signout for all clients
#the config class in identity server#
//MVC Client
new Client
{
ClientName = ".NET 4 MVC website",
ClientId = "net4mvcclient",
ClientSecrets =
{
new Secret("secret3".Sha256())
},
//Grant types are a way to specify how a client wants to interact with IdentityServer
AllowedGrantTypes = GrantTypes.Implicit,
RequireConsent = false,
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
RedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}/signin-oidc" },
PostLogoutRedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}/" },
AllowedScopes = {"openid", "profile", "offline_access", "api1", "api2" } ,
AllowedCorsOrigins = {$"{_config.GetValue<string>("IdentityServerSettings:MVCBaseUri")}"},
AccessTokenLifetime = 50000
},
//angular_spa
new Client {
RequireConsent = false,
ClientId = "angular_spa",
ClientName = "Angular SPA",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = { "openid", "profile", "offline_access", "api1", "api2" },
RedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}/#auth-callback/#" },
PostLogoutRedirectUris = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}/" },
AllowedCorsOrigins = { $"{_config.GetValue<string>("IdentityServerSettings:AngularBaseUri")}" },
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = 3600
}
};
startup class in MVC client
public void ConfigureIdentityServer(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
Authority = ConfigurationManager.AppSettings["IdentityServerUrl"], //Identity server Url
ClientId = "net4mvcclient",
ClientSecret = "secret3",
RedirectUri = ConfigurationManager.AppSettings["HourlyMVCUrl"] +"/signin-oidc", //Net4MvcClient's URL
PostLogoutRedirectUri = ConfigurationManager.AppSettings["HourlyMVCUrl"]+"/", //MVC Client URL
ResponseType = "id_token token",
RequireHttpsMetadata = false,
Scope = "openid profile api1 api2 offline_access",
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
return Task.FromResult(0);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var id_token_claim = n.OwinContext.Authentication.User.Claims.FirstOrDefault(x => x.Type == "id_token");
if (id_token_claim != null)
{
n.ProtocolMessage.IdTokenHint = id_token_claim.Value;
}
}
return Task.FromResult(0);
}
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
app.UseNLog((eventType) => LogLevel.Debug);
}
#the logout function in identity server#
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
// Request.GetOwinContext().Authentication.SignOut();
await HttpContext.SignOutAsync();
await _signInManager.SignOutAsync();
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (vm.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = vm.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
}
if (vm.PostLogoutRedirectUri != null)
{
return SignOut(new AuthenticationProperties { RedirectUri = vm.PostLogoutRedirectUri }, vm.ExternalAuthenticationScheme);
}
return View("LoggedOut", vm);
}

IdentityServer4 redirect callback cors error

in my identityserver4 redirect callback i get cors error.
i enabled cors policy, and used identityserver4 defaultcors class with AllowAll to true.
and i have my domain name in AllowedCorsOrigins in client config.
i tried any way and all articles but it always give cors error.
but if i try to make ajax request in that callback page it dont give me cors error to same page.
i cant understand it.
i have http://ids.example.com
and i have http://example.com
i there anything i can do to solve it. and this configuration works perfect in localhost.
Ids Config:
services.AddSingleton<ICorsPolicyService>(container =>
{
var logger = container.GetRequiredService<ILogger<DefaultCorsPolicyService>>();
return new DefaultCorsPolicyService(logger)
{
AllowAll = true
};
});
Client Config:
new Client
{
ClientId = "js.admin.prod",
ClientName = "Instrumententafel",
RequireClientSecret = false,
RequireConsent = false,
AllowedGrantTypes = GrantTypes.Code,
AccessTokenType = AccessTokenType.Reference,
// AccessTokenLifetime = 299,
RequirePkce = true,
AllowPlainTextPkce = false,
AllowAccessTokensViaBrowser = true,
AllowOfflineAccess = true,
UpdateAccessTokenClaimsOnRefresh = true,
RedirectUris = new List<string>
{
"http://example.com",
"http://example.com/logincb.html",
"http://example.com/silent-renew.html"
},
PostLogoutRedirectUris = new List<string>
{
"http://example.com/",
"http://example.com"
},
AllowedCorsOrigins = new List<string>
{
"http://example.com"
},
AllowedScopes = new List<string>
{
"openid",
"profile",
"email",
"phone",
"role",
"api1.full"
}
}
logincb.html:
<script src="./oidc-client.js"></script>
<script crossorigin>
var mgr = new Oidc.UserManager({
response_mode: "query",
userStore: new Oidc.WebStorageStateStore({ store: window.localStorage })
})
.signinRedirectCallback()
.then(function(user) {
console.log("signin response success", user);
window.location.href = "/";
})
.catch(function(err) {
console.log(err);
});
</script>
and the error is:
Access to XMLHttpRequest at 'http://ids.example.com/connect/token' from origin 'http://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
logincb.html?code=396EA4E3D60DFF3620FAB600F3F0DB8AC7DC5CC7922341A3F8E7A3E973E10705&scope=openid profile email phone role offline_access api1.full&state=7fc1f62c72034eddab48a1cac1d261f2&session_state=rQ_HS6gus4BkM7tmL_xgJbWwLhm83nMbks1NCQYymBo.CC889AAAE84EAA20ABD2DFD643BB7797:22 Error: Network Error
at XMLHttpRequest.req.onerror (JsonService.js:179)

Always getting "invalid_client" from Identity Server

Everything seems normal but it is not working, It returns "Invalid_Client" - (400 - Bad request).
Both side so simple below;
Identity Server Code:
new Client
{
ClientId = "js",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
RequireClientSecret = false,
AllowedScopes =
{
"api1"
}
}
Javascript Client Code:
axios.post('http://localhost:5000/connect/token',request, {
headers: {
'client_id' : 'js',
'client_secret' : 'secret',
'grant_type': 'client_credentials',
'scope' : 'api1'
}});
The parameters should be passed in request body not request header , you can modify the client script as :
const params = new URLSearchParams();
params.append('client_id', 'js');
params.append('client_secret', 'secret');
params.append('grant_type', 'client_credentials');
params.append('scope', 'api1');
axios.post('http://localhost:5000/connect/token', params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
}).then(function (response) {
console.log(response.data);
});
And also configure CORS is to use the AllowedCorsOrigins collection on the client configuration:
new Client
{
ClientId = "js",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedCorsOrigins= new List<string>() { "http://localhost:5002" },
AllowedGrantTypes = GrantTypes.ClientCredentials,
RequireClientSecret = false,
AllowedScopes =
{
"api1"
}
},
Should not use normall fetch or axis.
Because the service is Oauth service and should be use 'client-oauth2' library.
Code Example:
var ClientOAuth2 = require("client-oauth2");
var authRequest = new ClientOAuth2({
clientId: IDENTITY_CONFIG.client_id,
clientSecret: IDENTITY_CONFIG.client_secret,
accessTokenUri: IDENTITY_CONFIG.token_endpoint,
scopes: [IDENTITY_CONFIG.grantType]
});
return authRequest.credentials.getToken();

IdentityServer4 RequestedClaimTypes is Empty

In my profile service why is RequestedClaimTypes Empty? I am expecting the profile claims to be requested. And per this they should contain FamilyName and Given Name claim types.
GetIdentityResources
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
}
Client
new Client
{
ClientId = "46a0ab4a-1321-4d77-abe5-98f09310df0b",
ClientName = "TypeScript SPA client",
RequireClientSecret = false, // if false this is a public client.
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:3000/callback" },
PostLogoutRedirectUris = { "http://localhost:3000/" },
AllowedCorsOrigins = { "http://localhost:3000" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
RequireConsent = false,
},
oidc-client configuration typescript
const myOidcClientSettings: OidcClientSettings = {
authority: `${protocol}//${hostname}:5000`,
client_id: '46a0ab4a-1321-4d77-abe5-98f09310df0b',
post_logout_redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/`,
redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/callback`,
response_type: 'id_token token',
scope: 'openid profile'
};
const myUserManagerSettings: UserManagerSettings = {
...myOidcClientSettings,
automaticSilentRenew: false,
filterProtocolClaims: true,
loadUserInfo: true,
monitorSession: false,
silent_redirect_uri: `${protocol}//${hostname}${port ? `:${port}` : ''}/callback`,
};
Inside the Login Post I add the following claims:
Claim[] claims =
{
new Claim(JwtClaimTypes.Name, $"{loginResponse.FirstName} {loginResponse.LastName}"),
new Claim(JwtClaimTypes.Email, loginResponse.EmailAddress),
new Claim(JwtClaimTypes.PhoneNumber, loginResponse.PhoneNumber),
new Claim(JwtClaimTypes.FamilyName, loginResponse.LastName),
new Claim(JwtClaimTypes.GivenName, loginResponse.FirstName),
//new Claim(JwtClaimTypes.AuthorizationCodeHash, aRequest.Password), // The Password will be need by the BFF but can NOT be sent to the Typescript client
};
await HttpContext.Authentication.SignInAsync(subjectId, userName, authenticationProperties, claims);
ProfileService
public Task GetProfileDataAsync(ProfileDataRequestContext aProfileDataRequestContext)
{
Logger.LogDebug("Get profile called for {subject} from {client} with {claimTypes} because {caller}",
aProfileDataRequestContext.Subject.GetSubjectId(),
aProfileDataRequestContext.Client.ClientName,
aProfileDataRequestContext.RequestedClaimTypes,
aProfileDataRequestContext.Caller);
if (aProfileDataRequestContext.RequestedClaimTypes.Any())
{
aProfileDataRequestContext.AddFilteredClaims(aProfileDataRequestContext.Subject.Claims);
}
return Task.FromResult(0);
}
Resulting User Info that profile does NOT contain the profile items: (Shortend for readability
"User info": {
"id_token": "eyJhbGciOiJSUzI1N",
"session_state": "M5uV9nYzvmlWjvpjmX--OOPcwAEeVesV7aG9ZO0svS8.8f757e9a033183149734adb156fbb39d",
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6",
"token_type": "Bearer",
"scope": "openid profile",
"profile": {
"sid": "4372a4cbb9938449a39d72db1a9fc6f0",
"sub": "TestDemo12#gmail.com",
"auth_time": 1505037917,
"idp": "local",
"amr": [
"pwd"
]
},
"expires_at": 1505042091,
"state": {
"returnUrl": "/en-us/test"
}
}
It looks like you have to include the following option to your client,
AlwaysIncludeUserClaimsInIdToken = true
So that your client will include the claims in the token.

Is there any relation between SaveTokens and PostLogoutRedirectUris?

If savetokens is set to false the PostLogoutRedirectUris is not working. What is the relation between these two? I use identityserver4 1.1 with asp.net core 1.1
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
RequireHttpsMetadata = false,
ClientId = "openIdConnectClient",
AuthenticationScheme = "oidc",
Authority = "https://localhost:44309/",
SignInScheme = "Cookies",
Scope = { "email" },
SaveTokens = true
});
new Client
{
ClientId = "openIdConnectClient",
ClientName = "Example Implicit Client Application",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
},
RedirectUris = new List<string>
{
"https://localhost:44378/signin-oidc"
},
PostLogoutRedirectUris = new List<string>
{
"https://localhost:44378/signout-callback-oidc"
},
}
If you check the logout spec
https://openid.net/specs/openid-connect-session-1_0.html#RedirectionAfterLogout
you will find out, that the id_token is required at logout time to be able to redirect back to the client application.
SaveTokens does this exactly for you - it stores the token in the cookie and send it back to the OP at logout time.