originalClaims, UserDetails user) {
64 | var profile = getUserFromUserDetails(user, User.class).getUserInfo();
65 | originalClaims.put(Claims.OpenIdClaims.PHONE_CLAIM, profile.getPhoneNumber());
66 | originalClaims.put(Claims.OpenIdClaims.PHONE_VERIFIED, profile.isEmailVerified());
67 | // add phone numbers
68 | // OidcUserInfo.builder()
69 | // .phoneNumber(profile.getPhoneNumber())
70 | // .phoneNumberVerified(profile.isPhoneNumberVerified() ? "true" : "false");
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/dev/rexijie/auth/tokenservices/openid/IDTokenGranter.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth.tokenservices.openid;
2 |
3 | import org.springframework.security.authentication.InsufficientAuthenticationException;
4 | import org.springframework.security.core.Authentication;
5 | import org.springframework.security.core.context.SecurityContextHolder;
6 | import org.springframework.security.oauth2.provider.*;
7 | import org.springframework.security.oauth2.provider.implicit.ImplicitTokenRequest;
8 | import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
9 | import org.springframework.util.Assert;
10 |
11 | import static org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames.ID_TOKEN;
12 |
13 | /*
14 | TODO - implement c-hash claim
15 | */
16 | /**
17 | * An implementation of {@link TokenGranter} that grants id_tokens
18 | * using the implicit token grant
19 | * #UNUSED
20 | *
21 | * @author Rex Ijiekhuamen
22 | */
23 | public class IDTokenGranter extends AbstractTokenGranter {
24 | private static final String GRANT_TYPE = ID_TOKEN;
25 |
26 | private IDTokenGranter(AuthorizationServerOidcTokenServices tokenServices,
27 | ClientDetailsService clientDetailsService,
28 | OAuth2RequestFactory requestFactory,
29 | String grantType) {
30 | super(tokenServices, clientDetailsService, requestFactory, grantType);
31 | }
32 |
33 | public IDTokenGranter(AuthorizationServerOidcTokenServices tokenServices,
34 | ClientDetailsService clientDetailsService,
35 | OAuth2RequestFactory requestFactory) {
36 | this(tokenServices, clientDetailsService, requestFactory, "id_token");
37 | }
38 |
39 | @Override
40 | protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest clientToken) {
41 |
42 | Authentication userAuth = SecurityContextHolder.getContext().getAuthentication();
43 | if (userAuth==null || !userAuth.isAuthenticated()) {
44 | throw new InsufficientAuthenticationException("There is no currently logged in user");
45 | }
46 | Assert.state(clientToken instanceof ImplicitTokenRequest, "An ImplicitTokenRequest is required here. Caller needs to wrap the TokenRequest.");
47 |
48 | OAuth2Request requestForStorage = ((ImplicitTokenRequest)clientToken).getOAuth2Request();
49 |
50 | return new OAuth2Authentication(requestForStorage, userAuth);
51 |
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/dev/rexijie/auth/tokenservices/openid/IdTokenGeneratingTokenEnhancer.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth.tokenservices.openid;
2 |
3 | import dev.rexijie.auth.constants.Scopes;
4 | import dev.rexijie.auth.model.User;
5 | import dev.rexijie.auth.model.token.IDToken;
6 | import dev.rexijie.auth.model.token.KeyPairHolder;
7 | import dev.rexijie.auth.service.UserService;
8 | import dev.rexijie.auth.tokenservices.JwtTokenEnhancer;
9 | import io.jsonwebtoken.Claims;
10 | import io.jsonwebtoken.impl.DefaultClaims;
11 | import org.springframework.beans.factory.annotation.Value;
12 | import org.springframework.security.core.Authentication;
13 | import org.springframework.security.jwt.JwtHelper;
14 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
15 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
16 | import org.springframework.security.oauth2.core.oidc.OidcIdToken;
17 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
18 | import org.springframework.security.oauth2.provider.OAuth2Request;
19 |
20 | import java.nio.charset.StandardCharsets;
21 | import java.security.MessageDigest;
22 | import java.time.Instant;
23 | import java.util.*;
24 |
25 | import static dev.rexijie.auth.util.TokenRequestUtils.isAuthorizationCodeRequest;
26 | import static dev.rexijie.auth.util.TokenRequestUtils.isImplicitRequest;
27 | import static dev.rexijie.auth.util.TokenUtils.getMessageDigestInstance;
28 | import static dev.rexijie.auth.util.TokenUtils.hashString;
29 | import static io.jsonwebtoken.Claims.AUDIENCE;
30 | import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.NONCE;
31 |
32 | /**
33 | * @author Rex Ijiekhuamen
34 | */
35 | public class IdTokenGeneratingTokenEnhancer extends JwtTokenEnhancer {
36 |
37 | private final IDTokenClaimsEnhancer enhancer;
38 | private final UserService userService;
39 | @Value("${oauth2.openid.implicit.enabled}")
40 | private final boolean implicitEnabled = false;
41 |
42 | public IdTokenGeneratingTokenEnhancer(UserService userService,
43 | IDTokenClaimsEnhancer enhancer,
44 | KeyPairHolder keyPairHolder) {
45 | super(keyPairHolder);
46 | this.userService = userService;
47 | this.enhancer = enhancer;
48 | }
49 |
50 | @Override
51 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
52 | OAuth2Request request = authentication.getOAuth2Request();
53 | if (!request.getScope().contains(Scopes.ID_SCOPE))
54 | return accessToken;
55 |
56 | if (isAuthorizationCodeRequest(request) || (implicitEnabled && isImplicitRequest(request)))
57 | return appendIdToken(accessToken, authentication);
58 |
59 | return accessToken; // return normal token for other grant types
60 | }
61 |
62 | /**
63 | * This method uses an access token to generate an ID token.
64 | * some claims are taken directly from the access toke and mapped to the ID token
65 | *
66 | * The ID token is generated with base claims, then depending on the scopes requested
67 | * a delegate {@link IDTokenClaimsEnhancer} populates the required claims
68 | *
69 | * @param accessToken access token
70 | * @param authentication authentication context containing the authentication request
71 | * @return IDToken
72 | */
73 | private OAuth2AccessToken appendIdToken(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
74 | OAuth2Request request = authentication.getOAuth2Request();
75 |
76 | String nonce = request.getRequestParameters().get(NONCE);
77 | Claims accessTokenClaims = new DefaultClaims(super.decode(accessToken.getValue()));
78 | accessTokenClaims.put(AUDIENCE, request.getClientId());
79 |
80 | OidcIdToken.Builder builder = OidcIdToken.withTokenValue(accessToken.getValue())
81 | .issuer(accessTokenClaims.getIssuer())
82 | .subject(accessTokenClaims.getSubject())
83 | .audience(Set.of(accessTokenClaims.getAudience()))
84 | .authorizedParty(request.getClientId())
85 | .nonce(nonce)
86 | .expiresAt(accessTokenClaims.getExpiration().toInstant())
87 | .accessTokenHash(generateAccessTokenHash(accessToken))
88 | .authorizationCodeHash(generateCodeHash(accessToken, authentication))
89 | .authTime(accessTokenClaims.getIssuedAt().toInstant())
90 | .issuedAt(Instant.now())
91 | .authenticationMethods(getAuthenticationMethods(authentication));
92 |
93 | String username = accessTokenClaims.getSubject();
94 | User user = userService.findUserByUsername(username);
95 |
96 | if (request.getScope().contains(Scopes.IDTokenScopes.PROFILE))
97 | builder.claims(claimsMap -> enhancer.addProfileClaims(claimsMap, user));
98 |
99 |
100 | if (request.getScope().contains(Scopes.IDTokenScopes.EMAIL))
101 | builder.claims(claimsMap -> enhancer.addEmailClaims(claimsMap, user));
102 |
103 | OidcIdToken oidcIdToken = builder.build();
104 | IDToken idToken = new IDToken(oidcIdToken);
105 |
106 | String idTokenString = super.encode(idToken, authentication);
107 |
108 | DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
109 | token.setAdditionalInformation(Map.of(IDToken.TYPE, idTokenString));
110 |
111 | return token;
112 | }
113 |
114 | // generates the at_hash
115 | protected String generateAccessTokenHash(OAuth2AccessToken accessToken) {
116 |
117 | String algorithm = getHashAlgorithmForToken(accessToken.getValue());
118 | MessageDigest MD5 = getMessageDigestInstance(algorithm);
119 | // - get ascii representation of the token
120 | byte[] asciiValues = accessToken.getValue().getBytes(StandardCharsets.US_ASCII);
121 |
122 | // - hash the ascii value using the jwt hashing algorithm
123 | byte[] hashedToken = MD5.digest(asciiValues);
124 |
125 | // get the first 128 bits (hash alg length / 2 === 256 / 2)
126 | byte[] bytes = Arrays.copyOf(hashedToken, 16);
127 |
128 | return Base64.getEncoder().encodeToString(bytes);
129 | }
130 |
131 | // generate the c_hash claim value
132 | protected String generateCodeHash(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
133 | OAuth2Request request = authentication.getOAuth2Request();
134 | Map requestParameters = request.getRequestParameters();
135 | String authorizationCode = requestParameters.get("code");
136 | if (authorizationCode == null) return null;
137 |
138 | String algorithm = getHashAlgorithmForToken(accessToken.getValue());
139 | byte[] hashedCode = hashString(algorithm, authorizationCode);
140 | byte[] bytes = Arrays.copyOf(hashedCode, 16);
141 |
142 | return Base64.getEncoder().encodeToString(bytes);
143 | }
144 |
145 | // you should override this
146 | // RS256 is used to sign tokens so the algorithm returns SHA-256
147 | protected String getHashAlgorithmForToken(String token) {
148 | Map headers = JwtHelper.headers(token);
149 | String tokenAlg = headers.get("alg");
150 | return "SHA-".concat(tokenAlg.substring(2));
151 | }
152 |
153 | protected List getAuthenticationMethods(Authentication authentication) {
154 | return List.of("user");
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/dev/rexijie/auth/util/AuthenticationUtils.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth.util;
2 |
3 | import dev.rexijie.auth.model.User;
4 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
5 |
6 | public class AuthenticationUtils {
7 |
8 | public static User extractUserFromAuthentication(OAuth2Authentication auth2Authentication) {
9 | return (User) auth2Authentication.getPrincipal();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/dev/rexijie/auth/util/ObjectUtils.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth.util;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 |
6 | import java.util.Collection;
7 | import java.util.Map;
8 | import java.util.concurrent.ConcurrentHashMap;
9 | import java.util.function.Consumer;
10 |
11 | public class ObjectUtils {
12 | private static final ObjectMapper objectMapper = new ObjectMapper();
13 |
14 | /**
15 | * Helper method to apply a function on an object if it is not null or empty.
16 | * The object if not null is then passed as a parameter in the function.
17 | *
18 | * @param object the object to check
19 | * @param function the function to apply
20 | * @param The type of the object
21 | */
22 | public static void applyIfNonNull(T object, Consumer function) {
23 | if (object == null) return;
24 | if (object instanceof Collection) {
25 | var collection = ((Collection>) object);
26 | if (collection.isEmpty()) return;
27 | }
28 | function.accept(object);
29 | }
30 |
31 | public static void applyIfNonEmpty(Collection object, Consumer> function) {
32 | if (object == null) return;
33 | if (object.isEmpty()) return;
34 | function.accept(object);
35 | }
36 |
37 | /**
38 | * Utility method to remove null elements from map
39 | */
40 | public static Map cleanMap(Map map) {
41 | Map returnedMap = new ConcurrentHashMap<>();
42 | for (String key : map.keySet())
43 | if (map.get(key) != null)
44 | returnedMap.put(key, map.get(key));
45 |
46 | return returnedMap;
47 | }
48 |
49 | public static Map toMap(T object) {
50 | return objectMapper.convertValue(object, new TypeReference<>() {});
51 | }
52 |
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/dev/rexijie/auth/util/TokenRequestUtils.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth.util;
2 |
3 | import org.springframework.security.oauth2.provider.AuthorizationRequest;
4 | import org.springframework.security.oauth2.provider.OAuth2Request;
5 |
6 | import static dev.rexijie.auth.constants.GrantTypes.AUTHORIZATION_CODE;
7 | import static dev.rexijie.auth.constants.GrantTypes.IMPLICIT;
8 |
9 | public class TokenRequestUtils {
10 |
11 | public static boolean isImplicitRequest(OAuth2Request request) {
12 | return request.getGrantType().equals(IMPLICIT);
13 | }
14 | public static boolean isImplicitRequest(AuthorizationRequest request) {
15 | return request.getResponseTypes().contains("token");
16 | }
17 |
18 | public static boolean isAuthorizationCodeRequest(OAuth2Request request) {
19 | return request.getGrantType().equals(AUTHORIZATION_CODE);
20 | }
21 | public static boolean isAuthorizationCodeRequest(AuthorizationRequest request) {
22 | return request.getResponseTypes().contains("code");
23 | }
24 |
25 | public static boolean isIdTokenRequest(AuthorizationRequest authorizationRequest) {
26 | return authorizationRequest.getResponseTypes().contains("id_token");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/dev/rexijie/auth/util/TokenUtils.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth.util;
2 |
3 | import lombok.NonNull;
4 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
5 | import org.springframework.util.SerializationUtils;
6 |
7 | import java.math.BigInteger;
8 | import java.nio.charset.StandardCharsets;
9 | import java.security.MessageDigest;
10 | import java.security.NoSuchAlgorithmException;
11 | import java.time.Instant;
12 | import java.time.LocalDate;
13 | import java.util.Base64;
14 | import java.util.Map;
15 | import java.util.Objects;
16 | import java.util.UUID;
17 |
18 | public class TokenUtils {
19 | public static String serializeAuthentication(@NonNull OAuth2Authentication auth2Authentication) {
20 | var authenticationByteArray = SerializationUtils.serialize(auth2Authentication);
21 | return Base64.getEncoder().encodeToString(authenticationByteArray);
22 | }
23 |
24 | public static OAuth2Authentication deserializeAuthentication(String authentication) {
25 | var authenticationBytes = Base64.getDecoder().decode(authentication);
26 | var deserializedAuthentication = SerializationUtils.deserialize(authenticationBytes);
27 | if (!(deserializedAuthentication instanceof OAuth2Authentication))
28 | throw new RuntimeException("invalid authentication object");
29 |
30 | return (OAuth2Authentication) deserializedAuthentication;
31 | }
32 |
33 | public static String generateHash(String value) {
34 | if (value == null) return null;
35 | try {
36 | var md5Digest = MessageDigest.getInstance("MD5");
37 | var tokenBytes = value.getBytes(StandardCharsets.UTF_8);
38 | tokenBytes = md5Digest.digest(tokenBytes);
39 | return String.format("%032x", new BigInteger(1, tokenBytes));
40 |
41 | } catch (NoSuchAlgorithmException exception) {
42 | throw new IllegalStateException("MD5 algorithm not available");
43 | }
44 | }
45 |
46 | public static MessageDigest getMessageDigestInstance(String algorithm) {
47 | try {
48 | return MessageDigest.getInstance(algorithm);
49 | } catch (NoSuchAlgorithmException ignored) {
50 | throw new RuntimeException("unable to get hash algorithm");
51 | }
52 | }
53 |
54 | public static byte[] hashString(String algorithm, String value) {
55 | return getMessageDigestInstance(algorithm)
56 | .digest(value.getBytes(StandardCharsets.US_ASCII));
57 | }
58 |
59 | public static Map toOpenIdCompliantMap(Map mutableMap) {
60 | mutableMap.keySet()
61 | .parallelStream()
62 | .forEach(key -> {
63 | if (mutableMap.get(key) instanceof Instant) {
64 | Instant instant = (Instant) mutableMap.get(key);
65 | mutableMap.put(key, instant.getEpochSecond());
66 | }
67 |
68 | if (mutableMap.get(key) instanceof LocalDate) {
69 | LocalDate localDate = (LocalDate) mutableMap.get(key);
70 | mutableMap.put(key, localDate.toString());
71 | }
72 |
73 | if (mutableMap.get(key) == null)
74 | mutableMap.remove(key);
75 | });
76 | return mutableMap;
77 | }
78 |
79 | public static String generateUUID() {
80 | return UUID.randomUUID().toString();
81 | }
82 |
83 | public static String getTokenFromAuthorizationHeader(String authorization) {
84 | Objects.requireNonNull(authorization);
85 | return authorization.substring(7);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/additional-spring-configuration-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "properties": [
3 | {
4 | "name": "oauth2.openid.implicit.enabled",
5 | "type": "java.lang.String",
6 | "description": "Enable ID token generation for implicit flow."
7 | }
8 | ] }
--------------------------------------------------------------------------------
/src/main/resources/application-dev.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8000
3 | spring:
4 | application:
5 | name: REX-AUTH
6 | data:
7 | mongodb:
8 | username: idea
9 | password: ideapass
10 | database: authserver
11 | auto-index-creation: true
12 |
13 | oauth2:
14 | openid:
15 | discovery:
16 | baseUri: http://127.0.0.1:8000
17 | implicit:
18 | enabled: true
19 |
--------------------------------------------------------------------------------
/src/main/resources/application-docker.yml:
--------------------------------------------------------------------------------
1 | oauth2:
2 | openid:
3 | implicit:
4 | enabled: ${ENABLE_IMPLICIT_ID_TOKEN}
5 | spring:
6 | application:
7 | name: AUTHENTICATION_SERVER
8 | data:
9 | mongodb:
10 | username: ${MONGO_USERNAME}
11 | password: ${MONGO_PASSWORD}
12 | database: ${MONGO_DATABASE}
13 | host: ${MONGO_HOST}
14 | port: 27017
15 | auto-index-creation: true
16 |
--------------------------------------------------------------------------------
/src/main/resources/application-test.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8000
3 | spring:
4 | application:
5 | name: REX-AUTH
6 | data:
7 | mongodb:
8 | username: idea
9 | password: ideapass
10 | database: authserver
11 | auto-index-creation: true
12 |
13 | oauth2:
14 | openid:
15 | implicit:
16 | enabled: true
17 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | oauth2:
2 | openid:
3 | discovery:
4 | baseUri: ${SERVER_URL}
5 | issuer: ${oauth2.openid.discovery.baseUri}/openid
6 | tokenEndpoint: ${oauth2.openid.discovery.baseUri}/oauth2/token
7 | tokenKeyEndpoint: ${oauth2.openid.discovery.baseUri}/oauth2/token_key
8 | userinfoEndpoint: ${oauth2.openid.discovery.issuer}/userinfo
9 | checkTokenEndpoint: ${oauth2.openid.discovery.baseUri}/oauth2/check_token
10 | revocationEndpoint: ${oauth2.openid.discovery.baseUri}/oauth2/revoke
11 | authorizationEndpoint: ${oauth2.openid.discovery.baseUri}/oauth2/authorize
12 | introspectionEndpoint: ${oauth2.openid.discovery.baseUri}/oauth2/introspect
13 | jwksUri: ${oauth2.openid.discovery.issuer}/.well-known/jwks.json
14 | userinfoSigningAlgSupported:
15 | - RS256
16 | idTokenSigningAlgValuesSupported:
17 | - RS256
18 | tokenEndpointAuthSigningAlgorithmsSupported:
19 | - RS256
20 | scopesSupported:
21 | - openid
22 | - profile
23 | - email
24 | - read
25 | - write
26 | subjectTypesSupported:
27 | - public
28 | - pairwise
29 | responseTypesSupported:
30 | - code
31 | - token
32 | - id_token
33 | - code token
34 | - code id_token
35 | - id_token token
36 | - code id_token token
37 | claimsSupported:
38 | - iss
39 | - sub
40 | - iat
41 | - azp
42 | - exp
43 | - scope
44 | - at_hash
45 | - c_hash
46 | - nonce
47 | grantTypesSupported:
48 | - authorization_code
49 | - implicit
50 | tokenEndpointAuthMethodsSupported:
51 | - client_secret_basic
52 | - client_secret_post
53 |
--------------------------------------------------------------------------------
/src/main/resources/static/css/confirmaccess.css:
--------------------------------------------------------------------------------
1 | form {
2 | position: relative;
3 | }
4 | .form-footer {margin: 20px -20px 0 -20px;}
5 | .button-row {
6 | position: absolute;
7 | bottom: 30px;
8 | right: 30px;
9 | }
10 | .approval-text {
11 | margin: 0;
12 | line-height: 1.6em;
13 | }
14 | .form-label {
15 | display: none;
16 | }
17 | .icon {
18 | width: 50px;
19 | }
20 | .icon > img {
21 | width: 100%;
22 | }
23 | .content {
24 | margin-left: 10px;
25 | }
26 | .title {
27 | margin-bottom: 0;
28 | font-size: 1.2rem;
29 | }
30 |
31 | .description {
32 | margin-top: 5px;
33 | }
34 |
35 | .mr10 {
36 | margin-right: 10px;
37 | }
38 | @media screen and (max-width: 767px) {
39 | .button-row {
40 | position: unset;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/resources/static/css/global.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --blue: #5e72e4;
3 | --indigo: #5603ad;
4 | --purple: #8965e0;
5 | --pink: #f3a4b5;
6 | --red: #f5365c;
7 | --orange: #fb6340;
8 | --yellow: #ffd600;
9 | --green: #2dce89;
10 | --teal: #11cdef;
11 | --cyan: #2bffc6;
12 | --white: #fff;
13 | --gray: #8898aa;
14 | --gray-dark: #32325d;
15 | --lighter: #e9ecef;
16 | --primary: #5e72e4;
17 | --secondary: #f7fafc;
18 | --success: #2dce89;
19 | --info: #11cdef;
20 | --warning: #fb6340;
21 | --danger: #f5365c;
22 | --light: #adb5bd;
23 | --dark: #212529;
24 | --default: #172b4d;
25 | --neutral: #fff;
26 | --darker: black;
27 | --breakpoint-xs: 0;
28 | --breakpoint-sm: 576px;
29 | --breakpoint-md: 768px;
30 | --breakpoint-lg: 992px;
31 | --breakpoint-xl: 1200px;
32 | --black: #161616;
33 | --border-radius: 0.375rem;
34 | --main-font: 'Slabo 27px', serif;
35 | --secondary-font: 'Roboto', sans-serif;
36 | --color-primary: darkslateblue;
37 | --color-secondary: darkslateblue;
38 | }
39 |
40 | * {
41 | box-sizing: border-box;
42 | font-family: var(--secondary-font);
43 | font-size: 16px;
44 | }
45 | *:focus {
46 | outline: unset;
47 | }
48 | body {
49 | display: flex;
50 | flex-direction: row;
51 | align-items: center;
52 | background-color: var(--light);
53 | }
54 | .text-center {
55 | text-align: center;
56 | }
57 |
58 | .header {
59 | font-family: var(--main-font);
60 | }
61 |
62 | input {
63 |
64 | }
65 |
66 | .button {
67 | padding: 10px;
68 | min-width: 80px;
69 | border-radius: var(--border-radius);
70 | border: 1px solid transparent;
71 | background-color: cadetblue;
72 | cursor: pointer;
73 | position: relative;
74 | text-transform: none;
75 | transition: background-color 0.15s ease;
76 | letter-spacing: 0.025em;
77 | font-size: 0.875rem;
78 | will-change: transform;
79 | box-shadow: 0 4px 6px rgba(50, 50, 93, 0.11), 0 1px 3px rgba(0, 0, 0, 0.08);
80 | }
81 |
82 | .btn-primary {
83 | background-color: var(--gray-dark);
84 | border-color: var(--gray-dark);
85 | color: var(--white);
86 | }
87 | .btn-primary:hover {
88 | transform: none;
89 | background-color: #505092;
90 | }
91 |
92 | .btn-secondary {
93 | border: 1px solid transparent;
94 | background-color: var(--white);
95 | color: var(--black);
96 | }
97 | .btn-secondary:hover {
98 | text-decoration: underline;
99 | font-weight: bold;
100 | }
101 |
102 | .form-container {
103 | background-color: var(--white);
104 | width: 400px;
105 | margin: 0 auto;
106 | border-radius: var(--border-radius);
107 | box-shadow:
108 | 0 2.8px 2.2px rgba(0, 0, 0, 0.034),
109 | 0 6.7px 5.3px rgba(0, 0, 0, 0.048),
110 | 0 12.5px 10px rgba(0, 0, 0, 0.06),
111 | 0 22.3px 17.9px rgba(0, 0, 0, 0.072),
112 | 0 41.8px 33.4px rgba(0, 0, 0, 0.086),
113 | 0 100px 80px rgba(0, 0, 0, 0.12)
114 | }
115 | .form {
116 | padding: 0 20px;
117 | background-color: var(--white);
118 | border-radius: var(--border-radius);
119 | margin: 0;
120 | --form-margin: 20px;
121 | }
122 | .form-header {
123 | font-size: 2rem;
124 | padding: 20px 30px;
125 | margin: 0 -20px 25px -20px;
126 | background-color: var(--gray-dark);
127 | border-top-left-radius: var(--border-radius);
128 | border-top-right-radius: var(--border-radius);
129 | color: var(--white);
130 | }
131 |
132 | .form-row {
133 | padding: 0 10px;
134 | margin-bottom: var(--form-margin);
135 | display: flex;
136 | flex-direction: column;
137 | }
138 |
139 | .form-row-linear {
140 | display: flex;
141 | flex-direction: row;
142 | margin-bottom: var(--form-margin);
143 | }
144 |
145 | .form-label {
146 | margin-bottom: 5px;
147 | margin-left: 2px;
148 | text-transform: uppercase;
149 | }
150 |
151 | .form-input {
152 | padding: 10px 10px 10px 10px;
153 | font-weight: normal;
154 | background: white;
155 | border: 1px solid #505092;
156 | outline: none;
157 | font-size: 1em;
158 | margin: 6px 0 17px 0;
159 | transition: border-color 0.5s ease;
160 | -webkit-transition: border-color 0.5s ease;
161 | border-radius: var(--border-radius);
162 | }
163 | .form-error {
164 | color: var(--red);
165 | }
166 | .form-footer {
167 | flex-direction: row;
168 | padding: 20px 30px;
169 | margin: 20px -20px 50px -20px;
170 | border-bottom-left-radius: var(--border-radius);
171 | border-bottom-right-radius: var(--border-radius);
172 | }
173 |
174 | /* Utility methods */
175 | .w-100 {width: 100%;}
176 | .mt-1 {margin-top: 1em;}
177 | .mb-0 {margin-bottom: 0;}
178 | .d-flex {display: flex;}
179 | .d-flex-row{display: flex;flex-direction: row;}
180 | .d-flex-col{display: flex;flex-direction: column;}
181 | .f-justify-end {
182 | justify-content: flex-end;
183 | }
184 | /* Utility methods end*/
185 |
186 | @media screen and (max-width: 767px) {
187 | * {font-size: 14px;}
188 | .form-header{padding:20px 28px;}
189 | .form-container{width:100vw;height:100%;}
190 | .form-row{padding:0;}
191 | .form-footer {padding: 20px 20px;}
192 | }
193 |
194 | @media screen and (max-width: 350px) {
195 | * {font-size: 12px;}
196 | }
197 |
--------------------------------------------------------------------------------
/src/main/resources/static/css/login.css:
--------------------------------------------------------------------------------
1 | .login-btn {flex: 1;}
--------------------------------------------------------------------------------
/src/main/resources/static/css/logout.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Recks11/spring-oauth2-authorization-server/fad45eec46982b68203e3509879c56ae58c4a833/src/main/resources/static/css/logout.css
--------------------------------------------------------------------------------
/src/main/resources/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Recks11/spring-oauth2-authorization-server/fad45eec46982b68203e3509879c56ae58c4a833/src/main/resources/static/img/favicon.ico
--------------------------------------------------------------------------------
/src/main/resources/static/img/read.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/img/write.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
80 |
--------------------------------------------------------------------------------
/src/main/resources/templates/confirmaccess.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CONFIRM ACCESS
8 |
9 |
10 |
12 |
13 |
14 |
15 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/src/main/resources/templates/error/400.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OAuth2
6 |
7 |
8 |
9 | Error!
10 | I don't know what you did, but that wasn't it chief. that's a 400 for you
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/templates/error/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OAuth2
6 |
7 |
8 |
9 | Error 404
10 | seriously, how on earth did you get here? (@ _ @` ).
11 | I don't know what you wanted but it sure isn't here
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/templates/error/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | OAuth2 500
6 |
7 |
8 |
9 | Error (._. )
10 | Okay something went wrong internally.. My bad. 500
11 |
12 |
--------------------------------------------------------------------------------
/src/main/resources/templates/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | LOG IN
8 |
9 |
10 |
12 |
13 |
14 |
15 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/resources/templates/logout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | LOG OUT
8 |
9 |
10 |
12 |
13 |
14 |
15 |
20 |
21 |
--------------------------------------------------------------------------------
/src/test/java/dev/rexijie/auth/Oauth2ServerApplicationTests.java:
--------------------------------------------------------------------------------
1 | package dev.rexijie.auth;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class Oauth2ServerApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------